Skip to content

CRA v1 + Hot Module Reload (HMR) + Redux #2317

Closed
@ro-savage

Description

@ro-savage
Contributor

I'd like to confirm the 'correct' way to have CRA + HMR + React now we are on version 1 and using webpack 2, and that all these thoughts are correct. They may be useful to others adding HMR.

Examples have been updated incorporating feedback

Why Hot Module Reload

Adding in HMR changes your application from full page refresh to refreshing just the app.
In most cases this will make the page reload faster.

If you are using external state management such as Redux, you can have your redux state remain when component changes are made.

Note: This is not same a component based hot-module-reloading where state within your react application remains unchanged when making changes to your source code. HMR will remove any component-based state. That is currently unsupported by CRA, more information see react-hot-loader and status post by gaereon.

Hot Module Reload without Redux

index.js

// regular imports
ReactDOM.render(<App /> , document.getElementById('root'))

if (module.hot) {
  module.hot.accept('./App', () => {
    ReactDOM.render(<App />, document.getElementById('root'))
  })
}

As seen here and in issue #897

Hot Module Reload with Redux (or similar state management)

index.js

// Normal imports
import { Provider } from 'react-redux'
import configureStore from './redux/configureStore'

const store = configureStore()

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

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

configureStore.js (or similar)

import { createStore } from 'redux'

import rootReducer from './reducers'

const configureStore = () => {
  const store = createStore(rootReducer)

  if (process.env.NODE_ENV !== "production") {
    if (module.hot) {
      module.hot.accept('./reducers', () => {
        store.replaceReducer(rootReducer)
      })
    }
  }

  return store
}

export default configureStore

Activity

gaearon

gaearon commented on May 22, 2017

@gaearon
Contributor

With Webpack 2 (which CRA now uses) this should be enough:

// regular imports
ReactDOM.render(<App /> , document.getElementById('root'))

if (module.hot) {
  module.hot.accept('./App', () => {
    ReactDOM.render(<App />, document.getElementById('root'))
  })
}

It's because App is live-bound to ES6 import in Webpack 2.

Same with the other example:

import { createStore } from 'redux'

import rootReducer from './reducers'

const configureStore = () => {
  const store = createStore(rootReducer)

  if (process.env.NODE_ENV !== "production") {
    if (module.hot) {
      module.hot.accept('./reducers', () => {
        store.replaceReducer(rootReducer)
      })
    }
  }

  return store
}

export default configureStore
bfncs

bfncs commented on Jun 11, 2017

@bfncs

Really helpful, thanks a lot! Note, that the component you are hot reloading (<App /> in your example) needs to be class component, it will not work with a pure functional component.

dreyks

dreyks commented on Jul 25, 2017

@dreyks

for some weird reason my state gets wiped clean on hot reloading. I'm using plain react, no redux

rmoorman

rmoorman commented on Jul 27, 2017

@rmoorman

Same here (but using a simple redux setup). When I change my App component, the state seems to be gone.
@ro-savage, @bfncs or @gaearon is there some working example on github that can be cloned and run (CRA + minimal redux)?

Edit: suddenly it started working ... and I have no idea why ...

nealoke

nealoke commented on Jul 27, 2017

@nealoke

@rmoorman would you be able to share a small repo where all the code is present? I've been trying to get this to work for a couple of hours now and can't succeed...

I can see that the HMR picks up the change in the rootReducer and it recompiles fine. But when I check to see if the changed reducer actually changed, I can see that it does not change anything.

Any help would be awesome...

nealoke

nealoke commented on Jul 27, 2017

@nealoke

@bfncs @gaearon Am I missing something here?

index.js

import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { AppContainer } from 'react-hot-loader';
import { Router, browserHistory } from 'react-router';

import configureStore from 'state/store';
import routes from 'routing/routes.js';

export const store = configureStore();

const App = () => (
	<Provider store={store}>
		<Router history={browserHistory} routes={routes} />
	</Provider>
);

const renderApp = Component => {
	render(
		<AppContainer>
			<Component />
		</AppContainer>,
		document.getElementById('root')
	)
};

renderApp(App);

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

Store.js

import { createStore, applyMiddleware, compose } from 'redux';

import { reduxBatch } from '@manaflair/redux-batch';
import ReduxThunk from 'redux-thunk';

import orm from './orm';
import bootstrapper from './bootstrapper';
import reducers from './reducers';

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

export const configureStore = () => {
	const store = createStore(reducers, bootstrapper(orm), composeEnhancers(reduxBatch, applyMiddleware(ReduxThunk), reduxBatch));
	
	if (process.env.NODE_ENV !== 'production') {
		if (module.hot) {
			module.hot.accept('./reducers', () => {
				const test = require('./reducers');
				store.replaceReducer(test);
			});
		}
	}
	
	return store;
};

export default configureStore;
rmoorman

rmoorman commented on Jul 27, 2017

@rmoorman

@nealoke here you go. Pretty basic. Whenever you change the app component (or the rootReducer), state is kept.

zanjs

zanjs commented on Aug 5, 2017

@zanjs

Thinks 😜

sdhhqb

sdhhqb commented on Aug 8, 2017

@sdhhqb

thanks! these help a lot!

nealoke

nealoke commented on Aug 8, 2017

@nealoke

@rmoorman thanks, but I can't find a difference in setup between yours and my snippet though 😞

@sdhhqb did you get it to work with reducers as well?

sdhhqb

sdhhqb commented on Aug 8, 2017

@sdhhqb

@nealoke yes, it's working, the store can update when I change reducers. I use gaearon's approach.

rmoorman

rmoorman commented on Aug 8, 2017

@rmoorman

@nealoke but does cloning the example repo and running the code work for you (the example is basically what @Gearon suggested)? I would suggest to take one of the working approaches and incrementally go from there towards your piece of code in order to find the culprit.

gnapse

gnapse commented on Sep 27, 2017

@gnapse
Contributor

the component you are hot reloading ( in your example) needs to be class component, it will not work with a pure functional component

@bfncs I just got it to work with a pure functional component. Perhaps something has changed since you made that comment?

9 remaining items

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @gnapse@onpaws@rmoorman@RavenHursT@gaearon

        Issue actions

          CRA v1 + Hot Module Reload (HMR) + Redux · Issue #2317 · facebook/create-react-app