Skip to content

Instantly share code, notes, and snippets.

@njj
Forked from andrewmclagan/actions.js
Created August 24, 2016 15:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save njj/dd3d3726a196e823c36831734d794967 to your computer and use it in GitHub Desktop.
Save njj/dd3d3726a196e823c36831734d794967 to your computer and use it in GitHub Desktop.
react-redux-universal-hot-example
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
},
};
}
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;
};
};
}
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