Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
ro-savage opened this issue May 22, 2017 · 17 comments
Closed

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

ro-savage opened this issue May 22, 2017 · 17 comments

Comments

@ro-savage
Copy link
Contributor

ro-savage commented May 22, 2017

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
@gaearon
Copy link
Contributor

gaearon commented May 22, 2017

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
Copy link

bfncs commented Jun 11, 2017

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.

@gaearon gaearon closed this as completed Jun 26, 2017
@dreyks
Copy link

dreyks commented Jul 25, 2017

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

@rmoorman
Copy link

rmoorman commented Jul 27, 2017

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
Copy link

nealoke commented Jul 27, 2017

@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
Copy link

nealoke commented Jul 27, 2017

@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
Copy link

rmoorman commented Jul 27, 2017

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

@zanjs
Copy link

zanjs commented Aug 5, 2017

Thinks 😜

@sdhhqb
Copy link

sdhhqb commented Aug 8, 2017

thanks! these help a lot!

@nealoke
Copy link

nealoke commented Aug 8, 2017

@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
Copy link

sdhhqb commented Aug 8, 2017

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

@rmoorman
Copy link

rmoorman commented Aug 8, 2017

@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
Copy link
Contributor

gnapse commented Sep 27, 2017

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?

@onpaws
Copy link

onpaws commented Oct 6, 2017

@gnapse @bfncs +1 - was also able to use a pure functional <App />.

@RavenHursT
Copy link

I'm not sure what I'm doing wrong here...

AFAICT, my index.js is the same as what I'm seeing here (apart from ConnectedRouter).. The app compiles and loads up in the browser w/o any errors.

But when I make changes to App, while I can see the HMR requests complete in the Network tab:

image

... Not only do I not see the changes applied.. But now the page won't even reload. I have to manually reload now, to see said changes.

Here's the content of my index.js, and store.js:

import React from 'react'
import ReactDOM from 'react-dom'
import './index.scss'
import App from './App'
import { Provider } from 'react-redux'
import { ConnectedRouter } from 'connected-react-router'
import createStore, { history } from './store'
import registerServiceWorker from './registerServiceWorker'
import 'bootstrap/dist/css/bootstrap.min.css'

const store = createStore()

registerServiceWorker()
const rootElem = document.getElementById('root')
const AppJsx = <Provider store={store}>
  <ConnectedRouter history={history}>
    <div>
      <App />
    </div>
  </ConnectedRouter>
</Provider>

ReactDOM.render(
  AppJsx,
  rootElem
)

if (module.hot) {
  module.hot.accept('./App', () => {
    ReactDOM.render(AppJsx, rootElem)
  })
}
import { createStore, applyMiddleware, compose } from 'redux'
import { connectRouter, routerMiddleware } from 'connected-react-router'
import thunk from 'redux-thunk'
import createHistory from 'history/createBrowserHistory'
import rootReducer from './modules'

export const history = createHistory()
const initialState = {}
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose

export default () => createStore(
  connectRouter(history)(rootReducer),
  initialState,
  composeEnhancer(
    applyMiddleware(
      thunk,
      routerMiddleware(history),
    ),
  ),
)

Any help would be greatly appreciated :-)

@jamesg1
Copy link

jamesg1 commented Aug 23, 2018

@RavenHursT I think this blog post will help you here, I found it a big help today for getting HMR to work on my project. https://medium.com/@brianhan/hot-reloading-cra-without-eject-b54af352c642

Your missing hot module replacement for your redux store. Eg:

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

I would suggest to also split up your createStore() function parameters.

@RavenHursT
Copy link

Cool! I'm gonna try that out! Thanks!

@lock lock bot locked and limited conversation to collaborators Jan 18, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests