23
votes

I use React + Redux + Webpack + WebpackDevserver. Once the hot loader is launched all my reducers are reseted to the initial state.

Can I keep somehow my reducers in the actual state?

My Webpack config contains:

entry: [
    "./index.jsx"
],
output: {
    filename: "./bundle.js"
},
module: {
    loaders: [
        {
            test: /\.js|\.jsx$/,
            exclude: /node_modules/,
            loaders: ["react-hot","babel-loader"],
         }
    ]
},
plugins: [
    new webpack.HotModuleReplacementPlugin()
]

My reducers stats with:

const initialState = {
...
}

export default function config(state = initialState, action) { ...

I start my Webpack Dev-Server just by:

"start": "webpack-dev-server",
4

4 Answers

21
votes

Assuming Babel 6, you need to do something along this:

import {createStore} from 'redux';
import rootReducer from '../reducers';

export default function configureStore(initialState) {
  const store = createStore(rootReducer, initialState);

  if(module.hot) {
    // Enable Webpack hot module replacement for reducers
    module.hot.accept('../reducers', () => {
      const nextReducer = require('../reducers/index').default;

      store.replaceReducer(nextReducer);
    });
  }

  return store;
}

You can see the approach in action at my redux demo.

5
votes

Check the code related to store creation - createStore(). The store must be built outside app.js, otherwise, ot will be FLUSHED each time on each HMR update.

Wrong:

// app.js

import React from 'react';
import ReactDOM from 'react-dom';
import { hot } from 'react-hot-loader';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/lib/integration/react';
import { AppWidget } from 'containers';
import createStore from 'store/create-store';

const { store, persistor } = createStore(); // <!--- NEW STORE ON HMR, BUG

const rootElement = window.document.getElementById('appWidget');

const render = (Component) => {
  ReactDOM.render(
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        <Component />
      </PersistGate>
    </Provider>,
    rootElement,
  );
};

render(process.env.NODE_ENV === 'development' ? hot(module)(AppWidget) : AppWidget);

Correct:

// app.js

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { AppWidget } from 'containers';
import store from 'store/create-store';
import { AppContainer } from 'react-hot-loader';

const rootElement = window.document.getElementById('appWidget');

const render = (Component) => {
  ReactDOM.render(
    <AppContainer>
      <Provider store={store}>
        <Component />
      </Provider>
    </AppContainer>,
    rootElement,
  );
};

render(AppWidget);

if (module.hot) {
  module.hot.accept();
}

// create-store.js

import { applyMiddleware, compose, createStore } from 'redux';
import createSagaMiddleware from 'redux-saga';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
import rootSaga from './sagas';

const doCreateStore = () => {
  const sagaMiddleware = createSagaMiddleware();
  const middleware = [
    thunk,
    sagaMiddleware,
  ];

  const store = createStore(
    rootReducer,
    compose(
      applyMiddleware(...middleware),
    ),
  );

  sagaMiddleware.run(rootSaga);

  return store;
};

export default doCreateStore(); // <!-- CREATE AND RETURN STORE, NO BUG

0
votes

Just shape your code like below where you render the app in the root element.

store.js:

export const store = createStore(rootReducer, integrateDevTools)

index.jsx:

This will do the trick.


ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root'),
)


if (module.hot) {
  module.hot.accept()
}
0
votes

Yes you can, actually if you use react-hot-loader you'll have that exactly result and much more.

react-hot-loader will make sure to only update the exactly three you had change, and also will keeps the state on redux.

Example: you're creating a modal that receives some info from API and then keeps the info on redux, if you change the text color you know you'll have to wait for the entire app refresh, then the browser render, then you've to open the modal, wait the API and etc. BUT with react-hot-loader, after you changed that color, your modal will be still open, with your current data, and just that color will be updated.

Following the steps from the package README:

1 - You need to install the package (yes doesn't need to be a dev dependency, the README explain with more details why)

npm install react-hot-loader

2 - Add it on .babelrc

// .babelrc
{
  "plugins": ["react-hot-loader/babel"]
}

3 - Import hot at the first line of App.js file (yes, above React, ReactDOM, above all) and then mark your root component as hot-exported

// App.js
import { hot } from 'react-hot-loader/root';
const App = () => <div>Hello World!</div>;
export default hot(App);

That's it, now you should have a hot reload that focus only at the last changes and keeps the redux state for you.

OBS: If you use hooks, please check out more details here on the docs