How to Dynamically Load Reducers for Code Splitting in a Redux Application

How to dynamically load reducers for code splitting in a Redux application?

Update: see also how Twitter does it.

This is not a full answer but should help you get started. Note that I'm not throwing away old reducers—I'm just adding new ones to the combination list. I see no reason to throw away the old reducers—even in the largest app you're unlikely to have thousands of dynamic modules, which is the point where you might want to disconnect some reducers in your application.

reducers.js

import { combineReducers } from 'redux';
import users from './reducers/users';
import posts from './reducers/posts';

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

store.js

import { createStore } from 'redux';
import createReducer from './reducers';

export default function configureStore(initialState) {
const store = createStore(createReducer(), initialState);
store.asyncReducers = {};
return store;
}

export function injectAsyncReducer(store, name, asyncReducer) {
store.asyncReducers[name] = asyncReducer;
store.replaceReducer(createReducer(store.asyncReducers));
}

routes.js

import { injectAsyncReducer } from './store';

// Assuming React Router here but the principle is the same
// regardless of the library: make sure store is available
// when you want to require.ensure() your reducer so you can call
// injectAsyncReducer(store, name, reducer).

function createRoutes(store) {
// ...

const CommentsRoute = {
// ...

getComponents(location, callback) {
require.ensure([
'./pages/Comments',
'./reducers/comments'
], function (require) {
const Comments = require('./pages/Comments').default;
const commentsReducer = require('./reducers/comments').default;

injectAsyncReducer(store, 'comments', commentsReducer);
callback(null, Comments);
})
}
};

// ...
}

There may be neater way of expressing this—I'm just showing the idea.

How to dynamically load reducers

as @lustoykov suggested, the way to load reducers dynamically is by store.replaceReducer(configureReducers(reducer))

This, easy to follow article explains how to setup dynamic reducers in detail. https://tylergaw.com/articles/dynamic-redux-reducers/

Here is my github repo with a completed solution. It follows the steps as given in article. https://github.com/sabbiu/dynamic-reducers-for-react/

Dynamically load redux reducers with react router 4

In react-router v4, for async injection of reducers, do the following:

In your reducer.js file add a function called createReducer that takes in the injectedReducers as arg and returns the combined reducer:

/**
* Creates the main reducer with the dynamically injected ones
*/
export default function createReducer(injectedReducers) {
return combineReducers({
route: routeReducer,
modal: modalReducer,
...injectedReducers,
});
}

Then, in your store.js file,

import createReducer from './reducers.js';

const store = createStore(
createReducer(),
initialState,
composedEnhancers
);
store.injectedReducers = {}; // Reducer registry

Now, in order to inject reducer in an async manner when your react container mounts, you need to use the injectReducer.js function in your container and then compose all the reducers along with connect.
Example component Todo.js:

// example component 
import { connect } from 'react-redux';
import { compose } from 'redux';
import injectReducer from 'filepath/injectReducer';
import { addToDo, starToDo } from 'containers/Todo/reducer';

class Todo extends React.Component {
// your component code here
}
const withConnect = connect(mapStateToProps, mapDispatchToProps);

const addToDoReducer = injectReducer({
key: 'todoList',
reducer: addToDo,
});

const starToDoReducer = injectReducer({
key: 'starredToDoList',
reducer: starToDo,
});

export default compose(
addToDoReducer,
starToDoReducer,
withConnect,
)(Todo);

React-Boilerplate is an excellent source for understanding this whole setup.You can generate a sample app within seconds.
The code for injectReducer.js, configureStore.js( or store.js in your case) and in fact this whole configuration can be taken from react-boilerplate. Specific link can be found here for injectReducer.js, configureStore.js.

How can I load only part of a Redux Store in a React Application

I implemented similar recently and found How to dynamically load reducers for code splitting in a Redux application? which features a link to http://nicolasgallagher.com/redux-modules-and-code-splitting/ where Nicolas describes how they did it at Twitter.

TL;DR You want lazy-loaded reducers for this. The approach described there is to have a class as a "reducer-registry". You register your reducer/s when you need to use it/them. The registry then calls a listener with a combined reducer which includes all the currently registered reducers. You attach a listener to the registry which calls replaceReducer on your store to update it's reducer.

My implementation is here.. https://github.com/lecstor/redux-helpers/blob/master/src/reducer-registry.ts

How to access the store for dynamic loading of reducer

Looks like the above approach is no longer supporter in react-redux v6.0.0. Here is another approach.

preloadedState when injecting asyncReducers

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);
};


Related Topics



Leave a reply



Submit