Search code examples
reactjsreduxreact-boilerplate

preloadedState when injecting asyncReducers


How is it possible to have both a preloadedState (hydrating from server) and inject reducers dynamically?

In react-boilerplate or How to dynamically load reducers for code splitting in a Redux application? there is the concept of reducers which are added dynamically, based on the page/components you are viewing. Extract from reducers.js:

export default function createReducer(asyncReducers) {
  return combineReducers({
    users,
    posts,
    ...asyncReducers
  });
}

While this works well when navigating from one page to another (or on a client-side only application); when hydrating data from the server I am encountering this error:

Unexpected property "comments" found in previous state received by the reducer. Expected to find one of the known reducer property names instead: "users", "posts". Unexpected properties will be ignored.

(where comments is the property name of the dynamically injected reducer)

The reason for this error is clear, since the preloadedState coming from the server (using React SSR) already contains comments and the initial reducer does not since this is added dynamically afterwards. The error disappears if I add comments to my combineReducers; however that means that at app initialisation I need to include all reducers; which is not ideal.


Solution

  • You should be able to use dummy reducers in place of dynamically loaded reducers which will be replaced when the real reducers are loaded.

    { comments: (state = null) => state }
    

    This can also be done automatically, depending on your implementation, as per http://nicolasgallagher.com/redux-modules-and-code-splitting/

    // Preserve initial state for not-yet-loaded reducers
    const combine = (reducers) => {
      const reducerNames = Object.keys(reducers);
      Object.keys(initialState).forEach(item => {
        if (reducerNames.indexOf(item) === -1) {
          reducers[item] = (state = null) => state;
        }
      });
      return combineReducers(reducers);
    };