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

Change examples to explicitly use replaceReducer() for hot reloading #667

Merged
merged 1 commit into from
Aug 31, 2015

Conversation

gaearon
Copy link
Contributor

@gaearon gaearon commented Aug 31, 2015

This changes the examples to call replaceReducer() directly where appropriate instead of relying on the current “magic” behavior of React Redux.

On the surface, this contradicts #350, but the real intention behind #350 is to reduce the public API and remove “magic” from hot reloading because this magic often gets in the way (#301, #340).

This change lets us:

  • remove getReducer() completely from the Store public API, which is a win;
  • remove the magic around hot reloading, which is another win;
  • remove the need for <Root> component everywhere, and thus make examples simpler.

We can decide later whether or not we want to move ahead with something like #625 or not—it doesn't matter because whatever solution we end up with, we'll definitely force you to use HMR API directly for hot reloading, like in these examples.

Because actually removing getReducer() is a breaking change, we will do it separately (potentially with other breaking changes).

@eenewbsauce
Copy link

@SirCmpwn I'm experiencing the same issue as you. Can you share your solution?

Many thanks.

@gaearon
Copy link
Contributor Author

gaearon commented Jan 28, 2016

Are you using Babel 5 or 6? @eenewbsauce

@ddevault
Copy link

My configureStore looks something like this, @eenewbsauce:

export default function configureStore(initialState) {
  const store = createStoreWithMiddleware(reducer, initialState);
  if (module.hot) {
    module.hot.accept('../reducers', () => 
      store.replaceReducer(require('../reducers').default)
    );
  }
  return store;
}

@eenewbsauce
Copy link

@gaearon I am using Babel 6.3.26
thank you for posting @SirCmpwn, the callback is still not hitting for me however. Any other ideas?

Here is my configureStore.js:

import React from 'react';
import { applyMiddleware, compose, combineReducers, createStore } from 'redux';
import thunk from 'redux-thunk';
import { syncHistory, routeReducer } from 'react-router-redux'
import { browserHistory } from 'react-router'
const historySyncMiddleware = syncHistory(browserHistory);
import createLogger from 'redux-logger'

import rootReducer from '../reducers'
import { DevTools } from '../components'

const createStoreWithMiddleware = compose(
  applyMiddleware(thunk, historySyncMiddleware, createLogger()),
  DevTools.instrument()
)(createStore);

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

  if (module.hot) {
    console.log('hit');
    module.hot.accept('../reducers', () => {
      console.log('hit inside');
     store.replaceReducer(require('../reducers').default)
   });
  }

  historySyncMiddleware.listenForReplays(store)

  return store;
}

and here is my reducers.index.js:

import { combineReducers } from 'redux';
import { routeReducer } from 'react-router-redux'

import count from './count';

const rootReducer = combineReducers({
  routing: routeReducer,
  count
});

export default rootReducer;

@ddevault
Copy link

I recall now that I had to make some other change, but I can't find the relevant commit in my git log. Sorry, man.

@gaearon
Copy link
Contributor Author

gaearon commented Jan 29, 2016

@eenewbsauce Please publish the project reproducing the issue in isolation.

@eenewbsauce
Copy link

Thanks for your help @SirCmpwn.

@gaearon Thank you very much for looking into this issue. Here is the repo: https://github.com/eenewbsauce/react-router-redux

@gaearon
Copy link
Contributor Author

gaearon commented Jan 29, 2016

@eenewbsauce I can't reproduce the problem:

Maybe you have some watcher issue like this. Verify whether Webpack rebuilds when you change files.

@eenewbsauce
Copy link

Thank you very much @gaearon. I'll take a peak and let you know.

@eenewbsauce
Copy link

@gaearon I was trying to hot reload the actions, without registering them in module.hot.accept()
It's all set now.
Thank you very much for your help!

@gaearon
Copy link
Contributor Author

gaearon commented Jan 29, 2016

Interested how you set it up.
Hot reloading actions used to work with React Hot Loader but it doesn't currently work with React Transform. This is a known limitation we hope to solve eventually.

@eenewbsauce
Copy link

@gaearon It seems that I was a bit misleading in my previous post. I didn't get HMR working with actions, only `reducers'. Thanks again for your help.

@gaearon
Copy link
Contributor Author

gaearon commented Feb 1, 2016

Ah, okay. This is expected.

@c089
Copy link

c089 commented Feb 4, 2016

I followed the instructions and got the hot reloading to work with browserify and livereactload, but I'm still see this warning:

<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/rackt/react-redux/releases/tag/v2.0.0 for the migration instructions.

Is this expected or is there something wrong with my setup?

@gaearon
Copy link
Contributor Author

gaearon commented Feb 4, 2016

I presume that livereactload, unlike Webpack, does not offer something like module.hot API to handle updates to a specific module. Maybe you could hack around it like this:

let store
if (window.__store) {
  window.__store.replaceReducer(reducer)
} else {
  window.__store = store = createStore(reducer)
}

@c089
Copy link

c089 commented Feb 4, 2016

I presume that livereactload, unlike Webpack, does not offer something like module.hot API to handle updates to a specific module.

Here's what my configureStore looks like - very similar to the webpack examples, and it seems to work as I would expect, despite the warning:

export default function configureStore(initialState) {
  const devTools = window.devToolsExtension ? window.devToolsExtension() : undefined;
  const store = createStore(reducer, initialState, devTools);

  if (module.onReload) {
    module.onReload(() => {
      store.replaceReducer(require('./reducers').default);
      return true;
    });
  }

  return store;
}

Ah I see, I'm not replacing a specific module, but rather on every change because other than module.hot, onReload only takes one argument, right?

@c089
Copy link

c089 commented Feb 4, 2016

Hm, according to this that code should not trigger the warning. I guess this is more of an issue in livereactload, so I'll open an issue there.

@ash-vd
Copy link

ash-vd commented Feb 22, 2016

I'm seeing the same warning as c089. The callback isn't being called, although hot-reloading does work... My createStore-code:

if(window.devToolsExtension) {
  var enhanceOptions = [
    window.devToolsExtension()
  ];
} else {
  var enhanceOptions = [
    DevTools.instrument(),
    persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/))
  ];
}

const enhancer = compose(
  applyMiddleware(...middleware),
  ...enhanceOptions
);

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

  if (module.hot) {
    console.log('hot is true');
    module.hot.accept('reducers', function() {
      console.log('hit inside');
      store.replaceReducer(require('reducers').default);
    });
  }

  syncMiddleware.listenForReplays(store);

  return store;
}

@gaearon
Copy link
Contributor Author

gaearon commented Feb 22, 2016

module.hot.accept('reducers') looks a bit weird. Shouldn’t that be a relative path?

@ash-vd
Copy link

ash-vd commented Feb 22, 2016

Ah sorry, should have mentioned that. In my webpack.config I have src defined in the moduleDirectories, inside resolve. The reducers folder is inside this src dir.

resolve: {
  root: path.join(__dirname, 'src'),
  modulesDirectories: ['node_modules', 'bower_components', 'src']
}

I tried replacing reducers with a relative path, such as ../../reducers, but that didn't help. When I replace it with a path that doens't exist, Webpack mentions that it cannot find this path, so I guess the settings are right.

@gaearon
Copy link
Contributor Author

gaearon commented Feb 22, 2016

Please publish a project reproducing the issue. Or you can start with our examples and work backwards.

@jcarenza
Copy link

jcarenza commented Jun 6, 2016

@gaearon I'm also getting the error <Provider> does not support changing 'store' on the fly... I tried implementing the fix mentioned above but am having the same problem where the module.hot.accept callback function is not firing. Any ideas?

configureStore.js

import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import RootReducer from './reducers';

export default () => {
    const persistedState = undefined;

    const store = createStore(
        RootReducer,
        persistedState,
        applyMiddleware(
            thunkMiddleware
        )
    );

    if (module.hot) {
        // Enable Webpack hot module replacement for reducers
        module.hot.accept('./reducers', () => {
            console.info('executing callback');
            const nextRootReducer = require('./reducers').default;
            store.replaceReducer(nextRootReducer);
        });
    }

    return store;
};

reducers/index.js

import { combineReducers } from 'redux';
import NotesReducer from './reducer_notes';

const rootReducer = combineReducers({
  notes: NotesReducer
});

export default rootReducer;

@HorseBinky
Copy link

I'm running into the same problem like jcarenza. This error occures when I manipulate the elements which are written into the redux store. With other components the hot reload works fine.

@Ganitzsh
Copy link

@jcarenza I'm having the exact same issue. Did you find a fix?

@giest4life
Copy link

@gaearon
The interesting thing to note for me is that I get one warning <Provider> does not support changingstoreon the fly but all subsequent reloads work without a hitch. I have setup a simple project that demonstrates this. Here's the repo: https://github.com/giest4life/react-redux-boilerplate

@maullerz
Copy link

@maullerz
Copy link

maullerz commented Oct 21, 2016

@Ganitzsh @HorseBinky @jcarenza @giest4life
I have had the same issue.
How to resolve <Provider> does not support changing 'store' on the fly
At first, this warning is about changing the store, not reducer.

For example, like in @jcarenza code - #667 (comment)
if you are importing function that creates store from separate module

import configureStore from './configureStore';

const store = configureStore();

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

You need to move module.hot code here from configureStore
Also, add line

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

to the reducers/index.js

@giest4life
Copy link

giest4life commented Nov 16, 2016

@maullerz

Thanks for that tip!
Do you have repo that I can look at?

@heldrida
Copy link

@maullerz mind sharing an example, please ?

I'm also getting "warning.js:14 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."

maullerz added a commit to maullerz/react-formal-examples that referenced this pull request Nov 21, 2016
@maullerz
Copy link

@giest4life @heldrida
an example looks like this commit - maullerz/react-formal-examples@69947a3

@heldrida
Copy link

heldrida commented Nov 21, 2016

I end up doing the following in the app entry point:

`import React from 'react';
import { render } from 'react-dom';
import { browserHistory } from 'react-router';
import { syncHistoryWithStore } from 'react-router-redux';
import { AppContainer } from 'react-hot-loader';
import Root from './root';
import configureStore from './store'

const store = configureStore({});
const history = syncHistoryWithStore(browserHistory, store);

render(
  <AppContainer>
  	<Root store={ store } history={ history } />
  </AppContainer>,
  document.getElementById('app')
);

if (module.hot) {
  module.hot.accept('./root', () => {
	const NextRoot = require('./root').default;
    render(
		<AppContainer>
      		<NextRoot store={ store } history={ history } />
      	</AppContainer>,
      document.getElementById('app')
    );
  });
}`

the store configurator:


import { browserHistory } from 'react-router';
import { createStore, applyMiddleware } from 'redux'
import { routerMiddleware } from 'react-router-redux';
import reducers from '../reducers'
import thunk from 'redux-thunk';

export default function configureStore(initialState) {
  const store = createStore(
    reducers,
    applyMiddleware(thunk),
    applyMiddleware(routerMiddleware(browserHistory))
  );

  if (module.hot) {
    // Enable Webpack hot module replacement for reducers
    module.hot.accept('../reducers', () => {
      const nextRootReducer = require('../reducers');
      store.replaceReducer(nextRootReducer);
    });
  }

  return store
}

the root component:


import React, { Component } from 'react';
import ReactDOM from "react-dom";
import { Router, browserHistory } from 'react-router';
import { routerMiddleware, push } from 'react-router-redux';
import routes from './routes';
import { Provider } from 'react-redux';
import configureStore from './store'

// include the stylesheet entry point
require('../sass/app.scss');

export default class Root extends Component {
  render() {
    return (
		<Provider store={ this.props.store }>
			<div>
				<Router history={ this.props.history } routes={ routes } />
			</div>
		</Provider>
    );
  }
}

@patoncrispy
Copy link

@heldrida That worked perfectly for me. I was passing routes as props, but I don't understand why that wouldn't work, technically speaking... Is it because hot reloading of routes isn't supported?

@giest4life
Copy link

@heldrida That worked perfectly!

@cezarderevlean
Copy link

This works like a charm:

reducers/index.js

import { combineReducers } from 'redux';

import reducerA from './reducerA';
import reducerB from './reducerB';
import reducerC from './reducerC';

const reducer = combineReducers({
    reducerA,
    reducerB,
    reducerC,
});

export default reducer;

index.js

import React from 'react';
import ReactDOM from 'react-dom';

import { AppContainer } from 'react-hot-loader';
import { createStore, compose } from 'redux';
import { Provider } from 'react-redux';
import reducer from './app/reducers/index';

import App from './app/App.jsx';

const store = createStore(reducer, compose);

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

render(App);

if (module.hot) {
    module.hot.accept('./app/App.jsx', () => { render(App); });
    module.hot.accept('./app/reducers/index', () => { store.replaceReducer(reducer); });
}

@reduxjs reduxjs deleted a comment from IlanBeloglovsky Jan 5, 2021
@reduxjs reduxjs locked as resolved and limited conversation to collaborators Jan 5, 2021
This pull request was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.