Redux: Hot reloading reducers

Created on 21 Jul 2016  路  1Comment  路  Source: reduxjs/redux

Hello,
I am developing an app to use native webpack HMR and Redux, _without_ react-hot-loader or react-transform. Packages in my project include:

  • react @ 15.2.1
  • react-dom @ 15.2.1
  • react-redux @ 4.4.5
  • react-router @ 2.6.0
  • react-router-redux @ 4.0.5
  • redux @ 3.5.2
  • webpack @ 2.1.0-beta.19
  • webpack-dev-middleware @ 1.6.1
  • webpack-hot-middleware @ 2.12.1

I am using functional components, and key files to the hot reloading are:

routes.js

export default (props) => (
    <Router history={props.history}>
        <Route path='/' component={App}>
            <IndexRoute component={Home} />
            <Route path='page' component={Page} />
        </Route>
    </Router>
);

components/index.js

const render = () => {
    ReactDOM.render(
        <Provider store={store}>
            <Routes history={history} />
        </Provider>,
        document.getElementById('app')
    );
}

if(process.env.NODE_ENV == 'development' && module.hot) {
    module.hot.accept(['./components/routes', './reducers'], () => {
        store.replaceReducer(require('./reducers').default);
        render();
    });
}

render();

I was able to hot reload dom elements successfully. However, I faced two minor issues when trying to hot reload reducers.

Issue 1
When the initial state of a reducer is updated, it does not get reflected in the DOM. Also, I get a <Router routes> warning in console.

export default (state = [
    { id: 1, text: 'Todo 1', checked: false },
    { id: 2, text: 'Todo 2', checked: false },
    { id: 3, text: 'Todo 3 HOT', checked: false }
    // text has been updated, but does not show in the DOM. 
    // Also, I get a warning '[react-router] You cannot change 
    // <Router routes>; it will be ignored'
], action) => {
    switch (action.type) {
        case 'TOGGLE_TODO':
            return state.map(todo => {
                if (todo.id === action.id) {
                    todo.checked = !todo.checked;
                }
                return todo;
            });
        default:
            return state;
    }
};

Issue 2
When the action of a reducer is updated, the function behaviour does get updated in the DOM. However, I get a <Router routes> warning in console.

export default (state = [
    { id: 1, text: 'Todo 1', checked: false },
    { id: 2, text: 'Todo 2', checked: false },
    { id: 3, text: 'Todo 3', checked: false }
], action) => {
    switch (action.type) {
        case 'TOGGLE_TODO':
            return state.map(todo => {
                if (todo.id === action.id) {
                    todo.checked = todo.checked;
                    // the behaviour of action TOGGLE_TODO is updated.
                    // However, I get a warning '[react-router] You cannot
                    // change <Router routes>; it will be ignored'
                }
                return todo;
            });
        default:
            return state;
    }
};

How could I resolve the issues? I wasn't sure if <Router routes> warning has anything to do with Redux, but I thought I would post it here since I could only observe it when hot reloading reducers. To view all the source files, please refer to this tag. Thank you in advance!

Most helpful comment

I had a similar situation with Redux Provider and found this solution.
The problem is that the Provider doesn't allow replacement of the store with a re-render.

My solution is to unmount the root element and re-render the Provider with the app, only in case of hot reloading.

const rootElement = document.getElementById('root');
let getRouter = router;

const renderApp = () => render(
  <Provider store={store}>
      <AppContainer>
        {getRouter(store, history)}
      </AppContainer>
  </Provider>,
  rootElement
);

if (module.hot) {
  // Enable Webpack hot module replacement
  module.hot.accept('./router', () => {
    getRouter = require('./router').default;
    unmountComponentAtNode(rootElement);

    renderApp();
  });
}

>All comments

I had a similar situation with Redux Provider and found this solution.
The problem is that the Provider doesn't allow replacement of the store with a re-render.

My solution is to unmount the root element and re-render the Provider with the app, only in case of hot reloading.

const rootElement = document.getElementById('root');
let getRouter = router;

const renderApp = () => render(
  <Provider store={store}>
      <AppContainer>
        {getRouter(store, history)}
      </AppContainer>
  </Provider>,
  rootElement
);

if (module.hot) {
  // Enable Webpack hot module replacement
  module.hot.accept('./router', () => {
    getRouter = require('./router').default;
    unmountComponentAtNode(rootElement);

    renderApp();
  });
}
Was this page helpful?
0 / 5 - 0 ratings