-
-
Save njj/dd3d3726a196e823c36831734d794967 to your computer and use it in GitHub Desktop.
react-redux-universal-hot-example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export function login(loginHandle, password) { | |
return { | |
types: [LOGIN_REQUEST, LOGIN_SUCCESS, LOGIN_FAILURE], | |
promise: (api) => api.post('/auth/login', { login: loginHandle, password }).then(response => { | |
setAuthCookie(response.token); // side effect pre success dispatch | |
return response; | |
}), | |
then: (response) => { | |
postLoginRedirect(browserHistory.push, response.user, response.organisation); // side effect post success dispatch | |
}, | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export default function asyncThunkMiddleware(api) { | |
return ({ dispatch, getState }) => { | |
return next => action => { | |
// #1 Enable traditional redux-thunks | |
if (typeof action === 'function') { | |
return action(dispatch, getState); | |
} | |
const { promise, then, types, ...rest } = action; // eslint-disable-line no-redeclare | |
// #2 Dispatch normal actions and skip this middleware | |
if (!promise) { | |
return next(action); | |
} | |
// #3 Create mock after function | |
if (!then) { | |
let then = () => {}; // eslint-disable-line | |
} | |
const [REQUEST, SUCCESS, FAILURE] = types; | |
// #4 Dispatch the request action | |
next({ ...rest, type: REQUEST }); | |
// #5 Execute the async api call and get returned promise | |
const actionPromise = promise(api(dispatch, getState), dispatch, getState); | |
actionPromise | |
.then((response) => { | |
// #6 Dispatch the success action with response | |
next({ ...rest, ...response, type: SUCCESS }); | |
// #7 Call after and pass along promise, allowing the thunk to execute "after" side effects | |
then(response, dispatch, getState); | |
}) | |
.catch((error) => { | |
// #8 Dispatch the error action with response | |
next({ ...rest, error, type: FAILURE }); | |
// #9 Call after and pass along promise, allowing the thunk to execute "after" side effects | |
then(error, dispatch, getState); | |
}); | |
return actionPromise; | |
}; | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'babel-polyfill'; | |
import Express from 'express'; | |
import React from 'react'; | |
import { renderToString } from 'react-dom/server'; | |
import config from './config'; | |
import favicon from 'serve-favicon'; | |
import compression from 'compression'; | |
import httpProxy from 'http-proxy'; | |
import path from 'path'; | |
import createStore from './redux/create'; | |
import { api } from 'utilities/api'; | |
import { Html } from 'containers'; | |
import http from 'http'; | |
import cookieParser from 'cookie-parser'; | |
import { match, createMemoryHistory, RouterContext } from 'react-router'; | |
import { syncHistoryWithStore } from 'react-router-redux'; | |
import { Provider } from 'react-redux'; | |
import getRoutes from './routes'; | |
import { LOAD_RECIEVE } from 'redux/modules/auth/auth'; | |
import { ENUMS_RECIEVE } from 'redux/modules/app'; | |
/* | |
|-------------------------------------------------------------------------- | |
| Server configuration / setup | |
|-------------------------------------------------------------------------- | |
*/ | |
const app = new Express(); | |
const server = new http.Server(app); | |
const proxy = httpProxy.createProxyServer({ | |
target: `http://${config.apiHost}:${config.apiPort}`, | |
changeOrigin: true, | |
}); | |
app.use(compression()); | |
app.use(favicon(path.join(__dirname, '..', 'static', 'favicon.ico'))); | |
app.use(Express.static(path.join(__dirname, '..', 'static'))); | |
app.use(cookieParser()); | |
/* | |
|-------------------------------------------------------------------------- | |
| Utility functions | |
|-------------------------------------------------------------------------- | |
*/ | |
/** | |
* Returns token from request cookie if present | |
* | |
* @return Object | |
*/ | |
function authTokenFromRequest(request) { | |
return request.cookies._token ? request.cookies._token : ''; | |
} | |
/** | |
* Returns initial state from the API server | |
* | |
* @return Object | |
*/ | |
function fetchInitialState(token) { | |
const getState = () => { return { auth: { token } }; }; | |
const mockDispatch = () => {}; | |
return api(mockDispatch, getState).get('/initialize'); | |
} | |
/** | |
* Dispatches initial state to the store | |
* | |
* @return Object | |
*/ | |
function dispatchInitialState(dispatch, fetchedState) { | |
if (fetchedState.auth) { | |
dispatch({ type: LOAD_RECIEVE, response: fetchedState }); | |
} | |
if (fetchedState.enumerables) { | |
dispatch({ type: ENUMS_RECIEVE, response: fetchedState }); | |
} | |
} | |
/** | |
* Renders HTML to string | |
* | |
* @return String | |
*/ | |
function renderHtml(renderer, store, component) { | |
const html = renderer(<Html assets={webpackIsomorphicTools.assets()} component={component} store={store} />); | |
return `<!doctype html> \n ${html}`; | |
} | |
/** | |
* Initializes the application | |
* | |
* @return String | |
*/ | |
function initializeApp(request, response, api) { | |
const memoryHistory = createMemoryHistory(request.url); | |
const store = createStore(memoryHistory, api); | |
const history = syncHistoryWithStore(memoryHistory, store); | |
const token = authTokenFromRequest(request); | |
fetchInitialState(token) | |
.catch(error => console.log('>> User has no auth: ', error)) | |
.then(fetchedState => { | |
dispatchInitialState(store.dispatch, fetchedState); | |
match({ routes: getRoutes(store), location: request.url, history }, (error, redirect, renderProps) => { | |
const component = ( | |
<Provider store={store} key="provider"> | |
<RouterContext {...renderProps} /> | |
</Provider> | |
); | |
global.navigator = { userAgent: request.headers['user-agent'] }; | |
if (error) { | |
response.status(500).send(error.message); | |
} else if (redirect) { | |
response.redirect(302, `${redirect.pathname} ${redirect.search}`); | |
} else if (renderProps) { | |
response.status(200).send(renderHtml(renderToString, store, component)); | |
} else { | |
response.status(404).send('Page not found.'); | |
} | |
}); | |
}); | |
} | |
/* | |
|-------------------------------------------------------------------------- | |
| Server routes | |
|-------------------------------------------------------------------------- | |
*/ | |
/** | |
* API proxy route | |
* | |
* @return Void | |
*/ | |
app.use('/api', (request, response) => { | |
proxy.web(request, response); | |
}); | |
/** | |
* Proxy error callback route | |
* | |
* @return Void | |
*/ | |
proxy.on('error', (error, request, response) => { | |
if (error.code !== 'ECONNRESET') { | |
console.error('proxy error', error); | |
} | |
if (! response.headersSent) { | |
response.writeHead(500, { 'content-type': 'application/json' }); | |
} | |
response.end(JSON.stringify({ error: 'proxy_error', reason: error.message })); | |
}); | |
/** | |
* Healthcheck route | |
* | |
* @return Void | |
*/ | |
app.get('/health-check', (request, response) => { | |
response.status(200).send('Everything is just fine...'); | |
}); | |
/** | |
* React application render route | |
* | |
* @return Void | |
*/ | |
app.use((request, response) => { | |
if (__DEVELOPMENT__) { | |
// Do not cache webpack stats: the script file would change since, hot module replacement is enabled in the development env | |
webpackIsomorphicTools.refresh(); | |
} | |
initializeApp(request, response, api); | |
}); | |
/* | |
|-------------------------------------------------------------------------- | |
| Init server | |
|-------------------------------------------------------------------------- | |
*/ | |
server.listen(config.port, (error) => { | |
if (error) { | |
console.error(error); | |
} | |
console.info('>> Server running at http://%s:%s', config.host, config.port); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment