React-redux-starter-kit: Inject reducer replays actions

Created on 20 Oct 2016  路  8Comments  路  Source: davezuko/react-redux-starter-kit

It appears that each time a reducer is injected, all of the previous actions are replayed inside the respective reducers. The actions are not dispatched again, but the reducers will appear to run the actions. This doesn't appear to affect the store or state, but I'm surprised to see the replay. Can anyone speak to what is going on here?

I replicated this in the starter kit by adding two console.logs()

// src/store/reducers.js - line 11
export const injectReducer = (store, { key, reducer }) => {
  console.log(`inject ${key}`)
  store.asyncReducers[key] = reducer
  store.replaceReducer(makeRootReducer(store.asyncReducers))
}
// src/routes/Counter/modules/counter.js - line 43
const ACTION_HANDLERS = {
  [COUNTER_INCREMENT] : (state, action) => {
    console.log('counter inc')
    return state + action.payload
  }
}

To view the replayed actions:

  1. Run the app and go to localhost:3000
  2. Click the link for Counter - console log of the counter module injected
  3. Click the button Increment - console log of the increment action
  4. Click the link for Home
  5. Click the link for Counter again - console log of the counter module injected with one console log of the increment action
  6. Click the button Increment - console log of the increment action
  7. Click the link for Home
  8. Click the link for Counter again - console log of the counter module injected with two console logs of the increment action

Here is the console output
counter-log

Most helpful comment

I thought I was going crazy the first time I came across this!!!

It may be an issue with the DevTools, but I think it's more exposed in a design flaw of how routes are created in the starter kit. Using Counter, here is a not so elegant way to address that - it would be helpful if the starter kit did this from the start as it does not look like a best practice to inject reducers every time getComponent() is called (which can be a LOT). If you are using a redux-saga it will multiply the events as they are accumulated, so an event will be replayed X times for X route visits:

import { injectReducer } from '../../store/reducers'

let counterReducersInjected = false

export default (store) => ({
  path: 'counter',
  getComponent (nextState, cb) {
    require.ensure([], (require) => {
      const Counter = require('./containers/CounterContainer').default

      if (!counterReducersInjected) {
        const reducer = require('./modules/counter').default
        injectReducer(store, { key: 'counter', reducer })
        counterReducersInjected = true
      }
      cb(null, Counter)
    }, 'counter')
  }
})

@justingreenberg - just wanted to also thank you at the same time for adding fractal design - it's great. maybe you already have a solution for this?

All 8 comments

Looks like the issue was logged here in the DevTools
https://github.com/gaearon/redux-devtools/issues/304

I thought I was going crazy the first time I came across this!!!

It may be an issue with the DevTools, but I think it's more exposed in a design flaw of how routes are created in the starter kit. Using Counter, here is a not so elegant way to address that - it would be helpful if the starter kit did this from the start as it does not look like a best practice to inject reducers every time getComponent() is called (which can be a LOT). If you are using a redux-saga it will multiply the events as they are accumulated, so an event will be replayed X times for X route visits:

import { injectReducer } from '../../store/reducers'

let counterReducersInjected = false

export default (store) => ({
  path: 'counter',
  getComponent (nextState, cb) {
    require.ensure([], (require) => {
      const Counter = require('./containers/CounterContainer').default

      if (!counterReducersInjected) {
        const reducer = require('./modules/counter').default
        injectReducer(store, { key: 'counter', reducer })
        counterReducersInjected = true
      }
      cb(null, Counter)
    }, 'counter')
  }
})

@justingreenberg - just wanted to also thank you at the same time for adding fractal design - it's great. maybe you already have a solution for this?

Yes, please see https://github.com/mxstbr/react-boilerplate/pull/976

I'm in jury duty on iPad but basically check reducer key in injectReducer

And you are very welcome 馃榾

thanks @justingreenberg - just posting here your fix, which goes in /src/store/reducers.js

 // Line 12
 if (Reflect.has(store.asyncReducers, key)) return;

This just bit me on a recent project. I was running into the issue of actions re-running after a route change because of injectReducer even though I'm using the latest starter-kit with the previous fix. Making this change helped me.

This has a minor annoying downside. If you make the change below, the initial state for reducers loaded through injectReducer is not immediately visible in the redux dev tools. Even though the initial state isn't visible in the "state" within the dev tools, it is actually in the app. The reducer will work correctly. The state for that reducer will be visible in the dev tools after the next action fires.

I suspect we should make this change in the starter-kit to prevent this from happening to others. Took me a while to track it down.

// src/store/createStore.js
// ...
  if (__DEV__) {
    const composeWithDevToolsExtension = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
    if (typeof composeWithDevToolsExtension === 'function') {
      composeEnhancers = composeWithDevToolsExtension({
        shouldHotReload: false
      })
    }
  }
//...

I have experienced this issue, even with the change https://github.com/davezuko/react-redux-starter-kit/commit/820ca897db802979be2ce46e4d016076744145a2. @heygrady is correct in that shouldHotReload needs setting to false, however I'm still having issues.

Essentially I'm having async issues, I've described over on the DevTools repo. To sum it up, my reducer isn't injecting synchronously for some reason.

injectReducer(store, { key: 'transactions', reducer })

console.log(store.getState().transactions) // undefined

setTimeout(() => {
    console.log(store.getState().transactions) // yay we have stuff!
}, 100)

I'd love to hear anyone's thoughts on this! Thanks.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

maxkrieger picture maxkrieger  路  4Comments

macnibblet picture macnibblet  路  3Comments

marcelloromanelli picture marcelloromanelli  路  5Comments

zeroc picture zeroc  路  4Comments

brandonmikeska picture brandonmikeska  路  4Comments