React Hot Reload with Redux
Asked Answered
W

1

7

I've gotten everything working with HMR / React Hot Reloader as it pertains to views. But after adding redux, react-redux, etc ... anytime I modify a view or reducer I get the following error:

<Provider> does not support changing `store` on the fly. It is most likely that you see this error because you updated to Redux 2.x and React Redux 2.x which no longer hot reload reducers automatically. See https://github.com/reactjs/react-redux/releases/tag/v2.0.0 for the migration instructions.

Following the link in the error leads to a 2 year old post about explicitly using replaceReducer, so that's what I did.

My versions:

"redux": "^3.7.2" "react-redux": "^5.0.6"

I have a feeling this is mostly due to my lack of understanding of where and how to place my module.hot.accept calls (and if you can have multiple?). Relevant code is below.

boot.js (entry point)

import { Provider } from 'react-redux';
import { AppContainer } from 'react-hot-loader';
import { ConnectedRouter } from 'react-router-redux';

import App from './App';

import { configureStore, history } from './store/configureStore';

let store = configureStore();

function renderApp() {
    ReactDOM.render(
      <AppContainer>
        <Provider store={store}>
          <ConnectedRouter history={history}>
            <App />
          </ConnectedRouter>
        </Provider>
      </AppContainer>
      , document.getElementById("app"));
}

renderApp();

if (module.hot) {
    module.hot.accept(() => {
        renderApp();
    })
}

configureStore.js

import createHistory from 'history/createBrowserHistory';
import { createStore, combineReducers, compose, applyMiddleware } from 'redux';
import { routerMiddleware, routerReducer } from 'react-router-redux';
import thunk from 'redux-thunk';

import DevTools from '../components/DevTools';

import * as rootReducer from '../services/rootReducer';

const composeEnhancers = compose;

export const history = createHistory();

const middleware = routerMiddleware(history);

export function configureStore(initialState) {
  const reducers = { ...rootReducer, router: routerReducer };

  const store =  createStore(
    combineReducers(reducers),
    initialState,
    composeEnhancers(
      applyMiddleware(middleware, thunk),
      DevTools.instrument()
    )
  );

  if (module.hot) {
    module.hot.accept('../services/rootReducer', () => {
      const nextRootReducer = require('../services/rootReducer').default;
      const finalReducer = {...nextRootReducer, router: routerReducer };
      store.replaceReducer(finalReducer);
    })
  }

  return store;
}

The module.hot.accept in my configureStore is never actually called, because the parent one in boot.js is. Can there be only 1?!

How do I get rid of this error?

Or, let me rephrase: How do I get rid of this error and properly setup a react hot loading environment with redux stores?

Possibly relevant Github issue:

https://github.com/reactjs/react-redux/issues/259

Weiweibel answered 17/11, 2017 at 4:59 Comment(0)
W
7

After further research, it turns out the problem was due to the scope of the accept call in boot.js. Since it was essentially watching EVERYTHING from the root level app, the entire app was reloaded on a reducer change, thus the reducer's HMR accept was never called. Below is the working version.

boot.js

if (module.hot) {
    module.hot.accept('./containers/App.js', () => {
        const nextApp = require('./containers/App').default;
        renderApp(nextApp);
    })
}

configureStore.js

  if (module.hot) {
    module.hot.accept('../services/rootReducer', () => {
      const nextRootReducer = require('../services/rootReducer');
      const finalReducer = {...nextRootReducer, router: routerReducer };
      store.replaceReducer(combineReducers(finalReducer));
    })
  }
Weiweibel answered 18/11, 2017 at 16:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.