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
Updating and Merging State Object Using React Usestate() Hook
Capture Value Out of Query String with Regex
Download File from Bytes in JavaScript
How to Define Setter/Getter on Prototype
Fix Node Position in D3 Force Directed Layout
Destructuring and Rename Property
Difference and Intersection of Two Arrays Containing Objects
Safe Evaluation of Arithmetic Expressions in JavaScript
How to Return a JavaScript String from a Webassembly Function
Using Number as "Index" (JSON)
JavaScript Can't Access Private Properties
Rendering React Components from Array of Objects
Use $Http Inside Custom Provider in App Config, Angular.Js
What Does the Function Then() Mean in JavaScript
Google Maps API V3 - Multiple Markers on Exact Same Spot
Convert Time Interval Given in Seconds into More Human Readable Form