erikras / react-redux-universal-hot-example

A starter boilerplate for a universal webapp using express, react, redux, webpack, and react-transform
MIT License
12k stars 2.5k forks source link

Reducer not hotloading #44

Closed emilchacko closed 9 years ago

emilchacko commented 9 years ago

case COUNTER_INCREMENT: let {count} = state; return { count: count +1 };

changed it to

case COUNTER_INCREMENT: let {count} = state; return { count: count +10 };

I'm getting the console log as:

[1] [piping] File src/reducers/counter.js has changed, reloading. [0] Hash: 9bc9239a8a2f86a42fe5 [0] Version: webpack 1.10.3 [0] Time: 215ms [0] Asset Size Chunks Chunk Names [0] main-9bc9239a8a2f86a42fe5.js 3.02 MB 0 [emitted] main [0] 0.4416de2fe6fb28b0fffb.hot-update.js 3.02 kB 0 [emitted] main [0] 4416de2fe6fb28b0fffb.hot-update.json 36 bytes [emitted]

but it doesn't get reflected when I try to increment it using the button .What am I missing ?

erikras commented 9 years ago

There's a discussion going on about this over on redux-devtools#24 and I've been working on it (unsuccessfully) on the reducer-reload-test branch. Any testing or debugging time you could put into it would be appreciated.

erikras commented 9 years ago

I haven't gone back to work on this. It doesn't affect me that much. It should be pretty easy to suss out for @gaearon, but he's so busy with the 1.0 docs.

gaearon commented 9 years ago

Ping me in a week or so?

erikras commented 9 years ago

Will do. I just had a head scratching moment caused by my reducer not hot reloading, so I retract my previous "this doesn't affect me" statement. :smile:

erikras commented 9 years ago

:speaker: Ping!

timdorr commented 9 years ago

So, the issue is with where the store is created, right? Why not create the store within the router file, rather than passing it in? You would pass in the initial state, not a fully composed store object, as the 3rd parameter.

If you need the store back outside of the router (which it looks like you do for the dev tools and SSR), include it in the resolve().

northerneyes commented 9 years ago

Based on todomvc example in redux-devtools I figured out that Hotloading for reducers stop working after we just add this line import * as reducers from './reducers'; to index.js file.

romabelka commented 9 years ago

Unfortunately it's not so easy. As @gaearon said, file that create store must:

Just moving store creation logic won't help - we still exporting Promise, not the root React component

northerneyes commented 9 years ago

Yeah, there is problem to create stores inside the Root React component, when we are using react-router, for example.

romabelka commented 9 years ago

More over for my App, that was created from scratch, I implemented all 3 points from @gaearon, but it didn't help. So I'll wait for docs with universal react-router

josebalius commented 9 years ago

@gaearon pinging again! :p any chance you can give us a hand here?

gaearon commented 9 years ago

Which branch should I try?

northerneyes commented 9 years ago

As I said, just add this line import * as reducers from './reducers'; to index.js file in your todomvc example in redux-devtools, and reducers hot reloading stop working. And need to reload server after that

erikras commented 9 years ago

@gaearon This is the branch where I tried to get it working, but you could start fresh from master, too.

gaearon commented 9 years ago

@erikras Getting this on master:

ROUTER ERROR:   TypeError: Cannot convert undefined or null to object

  - Function.keys

  - Html.js:40 Html.render
    /Users/dan/p/react-redux-universal-hot-example/src/Html.js:40:19

  - ReactCompositeComponent.js:789 [object Object].ReactCompositeComponentMixin.    _renderValidatedComponentWithoutOwnerOrContext
    [react-redux-universal-hot-example]/[react]/lib/ReactCompositeComponent.js:7    89:34
gaearon commented 9 years ago

As I said, just add this line import * as reducers from './reducers'; to index.js file in your todomvc example in redux-devtools, and reducers hot reloading stop working.

That's the expected behavior. :-)

erikras commented 9 years ago

@gaearon Very strange. I just blew away my webpack-stats.json, node_modules, ran npm install, and master is working for me with npm run dev.

gaearon commented 9 years ago

Eh, I ran npm start. :-P Yeah I can run it now. Will investigate.

gaearon commented 9 years ago

The simplest fix is to use Webpack's HMR API directly:

/* global __DEVELOPMENT__, __CLIENT__, __DEVTOOLS__ */
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import createMiddleware from './clientMiddleware';
import * as reducers from '../reducers/index';

let reducer = combineReducers(reducers);
let lastCreatedStore; // <------------------------------- remember store

if (module.hot) {
  module.hot.accept('../reducers/index', () => {
    reducer = combineReducers(require('../reducers/index'));
    lastCreatedStore.replaceReducer(reducer);
  });
}

export default function(client, data) {
  const middleware = createMiddleware(client);
  let finalCreateStore;
  if (__DEVELOPMENT__ && __CLIENT__ && __DEVTOOLS__) {
    const { devTools, persistState } = require('redux-devtools');
    finalCreateStore = compose(
      applyMiddleware(middleware),
      devTools(),
      persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/)),
      createStore
    );
  } else {
    finalCreateStore = applyMiddleware(middleware)(createStore);
  }
  const store = finalCreateStore(reducer, data);
  store.client = client;
  lastCreatedStore = store; // <------------------ remember store
  return store;
}

We'll have a better fix later, but you can use this for now.

erikras commented 9 years ago

Awesome, @gaearon!!! I knew it'd be a matter of minutes for you. :smile:

northerneyes commented 9 years ago

Work like a charm, huge thanks!

gaearon commented 9 years ago

Simpler way to write the same thing:

/* global __DEVELOPMENT__, __CLIENT__, __DEVTOOLS__ */
import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
import createMiddleware from './clientMiddleware';

export default function(client, data) {
  const middleware = createMiddleware(client);
  let finalCreateStore;
  if (__DEVELOPMENT__ && __CLIENT__ && __DEVTOOLS__) {
    const { devTools, persistState } = require('redux-devtools');
    finalCreateStore = compose(
      applyMiddleware(middleware),
      devTools(),
      persistState(window.location.href.match(/[?&]debug_session=([^&]+)\b/)),
      createStore
    );
  } else {
    finalCreateStore = applyMiddleware(middleware)(createStore);
  }

  const reducer = combineReducers(require('../reducers/index'));
  const store = finalCreateStore(reducer, data);
  store.client = client;

  if (module.hot) {
    module.hot.accept('../reducers/index', () => {
      const nextReducer = combineReducers(require('../reducers/index'));
      store.replaceReducer(nextReducer);
    });
  }

  return store;
}

Note that module.hot.accept is mutative and calling it many times replaces the hot update handler. This means that hot replacement will only work for the store you create() last. But in Redux you should have one store anyway, so not a problem.

josebalius commented 9 years ago

@gaearon @erikras thank you guys

gaearon commented 9 years ago

No problem, sorry it took me so long to come back to this.