Skip to content

Instantly share code, notes, and snippets.

@vdh
Created September 26, 2016 23:55
Show Gist options
  • Save vdh/c241560382d7b3c4aebf68aac66b9446 to your computer and use it in GitHub Desktop.
Save vdh/c241560382d7b3c4aebf68aac66b9446 to your computer and use it in GitHub Desktop.
Moderately-successful hot reloading for react-router v3
// Stripped down because you probably have your own setup for this sort of stuff already…
export default function configureStore() {
// Setup the Redux store, perhaps also using react-router-redux,
// or maybe not even using Redux at all…
// history is either browserHistory from react-router
// or some other history generation…
return { store, history };
}
export default function hotReloadRoutes(routes, nextRoutes, keyDebug = []) {
/* eslint-disable no-console */
if (!routes || typeof routes !== 'object') {
console.warn('No matching routes available to hot reload into…');
return;
}
if (!nextRoutes || typeof nextRoutes !== 'object') {
console.warn('No matching new routes available to hot reload into…');
return;
}
if (Array.isArray(routes)) {
if (!Array.isArray(nextRoutes)) {
console.warn(
'Old routes is an array but the next routes are not:',
keyDebug.join('.')
);
return;
}
const length = routes.length;
const nextRoutesLength = nextRoutes.length;
if (nextRoutesLength !== length) {
console.warn(
'Old routes is an array of', length,
'but the next routes are an array of ', nextRoutesLength, ':',
keyDebug.join('.')
);
return;
}
for (let index = 0; index < length; index++) {
const childRoute = routes[index];
const nextChildRoute = routes[index];
const newKeyDebug = keyDebug.slice(0);
const lastKeyDebugIndex = newKeyDebug.length - 1;
newKeyDebug[lastKeyDebugIndex] = `${newKeyDebug[lastKeyDebugIndex]}[${index}]`;
// Ignore array items without paths
if (!childRoute.path) { continue; }
// Warn if paths don't match up between arrays
if (childRoute.path !== nextChildRoute.path) {
console.warn(
'Old child route has path', childRoute.path,
'but the next child route has path ', nextChildRoute.path, ':',
newKeyDebug.join('.')
);
break;
}
hotReloadRoutes(childRoute, nextChildRoute, newKeyDebug);
}
return;
}
const keys = Object.keys(routes);
for (const key of keys) {
const nextSubRoutes = nextRoutes[key];
const newKeyDebug = keyDebug.concat([key]);
if (key === 'component' && nextSubRoutes) {
// eslint-disable-next-line no-param-reassign
routes.component = nextSubRoutes;
} else {
const subRoutes = routes[key];
// Ignore plain data…
if (typeof subRoutes !== 'object') { return; }
// Check for missing data…
if (!nextSubRoutes || typeof nextSubRoutes !== 'object') {
console.warn(
'No matching new routes available for subkey:',
newKeyDebug.join('.')
);
return;
}
// Recursion…
hotReloadRoutes(subRoutes, nextSubRoutes, newKeyDebug);
}
}
}
import React from 'react';
import { render } from 'react-dom';
import configureStore from './configureStore';
import routes from './routes';
import Root from './Root';
// Development only
import hotReloadRoutes from './hotReloadRoutes';
const props = configureStore(); // { store, history }
render(
<AppContainer>
<Root {...props} routes={routes} />
</AppContainer>,
document.getElementById('root')
);
if (module.hot) {
module.hot.accept([
'./Root',
'./routes',
], () => {
/* eslint-disable global-require */
const NextRoot = require('./Root').default;
const nextRoutes = require('./routes').default;
/* eslint-enable global-require */
// Patch the existing routes object with hot-reloaded components
hotReloadRoutes(routes, nextRoutes);
// Re-render the Root component via the react-hot-loader container
render(
<AppContainer>
<NextRoot {...props} routes={routes} />
</AppContainer>,
document.getElementById('root')
);
});
}
/* eslint-disable global-require */
if (process.env.NODE_ENV === 'production') {
module.exports = require('./index.prod');
} else {
module.exports = require('./index.dev');
}
import React from 'react';
import { render } from 'react-dom';
import configureStore from './configureStore';
import routes from './routes';
import Root from './Root';
const props = configureStore(); // { store, history }
render(
<Root {...props} routes={routes} />,
document.getElementById('root')
);
import React, { PropTypes } from 'react';
import { Provider } from 'react-redux'; // Or something else, if you're not using Redux
import Router from 'react-router/lib/Router';
const Root = ({ store, history, routes }) => (
<Provider store={store} key="provider">
<Router history={history} routes={routes} />
</Provider>
);
Root.propTypes = {
store: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
routes: PropTypes.object.isRequired,
};
export default Root;
export default {
// Insert routes config here…
// Anything imported/required here will trigger the custom hot reloading when they attempt to reload
};
@leebenson
Copy link

This is the only solution that actually worked for me. Thanks @vdh!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment