It turns out that Redux lets us combine multiple reducers into one that can be passed into createStore by using a helper function named combineReducers . The way we combine reducers is simple, we create one file per reducer in the reducers directory. We also create a file called index. js inside the reducers directory.
The combineReducers helper function turns an object whose values are different reducing functions into a single reducing function you can pass to createStore .
If the application's state is managed by Redux, the changes happen inside a reducer function — this is the only place where state changes happen. The reducer function makes use of the initial state of the application and something called action, to determine what the new state will look like.
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.
import { combineReducers } from 'redux';
import users from './reducers/users';
import posts from './reducers/posts';
export default function createReducer(asyncReducers) {
return combineReducers({
users,
posts,
...asyncReducers
});
}
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));
}
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.
This is how I implemented it in a current app (based on code by Dan from a GitHub issue!)
// Based on https://github.com/rackt/redux/issues/37#issue-85098222
class ReducerRegistry {
constructor(initialReducers = {}) {
this._reducers = {...initialReducers}
this._emitChange = null
}
register(newReducers) {
this._reducers = {...this._reducers, ...newReducers}
if (this._emitChange != null) {
this._emitChange(this.getReducers())
}
}
getReducers() {
return {...this._reducers}
}
setChangeListener(listener) {
if (this._emitChange != null) {
throw new Error('Can only set the listener for a ReducerRegistry once.')
}
this._emitChange = listener
}
}
Create a registry instance when bootstrapping your app, passing in reducers which will be included in the entry bundle:
// coreReducers is a {name: function} Object
var coreReducers = require('./reducers/core')
var reducerRegistry = new ReducerRegistry(coreReducers)
Then when configuring the store and routes, use a function which you can give the reducer registry to:
var routes = createRoutes(reducerRegistry)
var store = createStore(reducerRegistry)
Where these functions look something like:
function createRoutes(reducerRegistry) {
return <Route path="/" component={App}>
<Route path="core" component={Core}/>
<Route path="async" getComponent={(location, cb) => {
require.ensure([], require => {
reducerRegistry.register({async: require('./reducers/async')})
cb(null, require('./screens/Async'))
})
}}/>
</Route>
}
function createStore(reducerRegistry) {
var rootReducer = createReducer(reducerRegistry.getReducers())
var store = createStore(rootReducer)
reducerRegistry.setChangeListener((reducers) => {
store.replaceReducer(createReducer(reducers))
})
return store
}
Here's a basic live example which was created with this setup, and its source:
It also covers the necessary configuration to enable hot reloading for all your reducers.
There is now a module that adds injecting reducers into the redux store. It is called Redux Injector.
Here is how to use it:
Do not combine reducers. Instead put them in a (nested) object of functions as you would normally but without combining them.
Use createInjectStore from redux-injector instead of createStore from redux.
Inject new reducers with injectReducer.
Here is an example:
import { createInjectStore, injectReducer } from 'redux-injector';
const reducersObject = {
router: routerReducerFunction,
data: {
user: userReducerFunction,
auth: {
loggedIn: loggedInReducerFunction,
loggedOut: loggedOutReducerFunction
},
info: infoReducerFunction
}
};
const initialState = {};
let store = createInjectStore(
reducersObject,
initialState
);
// Now you can inject reducers anywhere in the tree.
injectReducer('data.form', formReducerFunction);
Full Disclosure: I am the creator of the module.
As of October 2017:
Reedux
implements what Dan suggested and nothing more, without touching your store, your project or your habits
There are other libraries too but they might have too many dependencies, less examples, complicated usage, are incompatible with some middlewares or require you to rewrite your state management. Copied from Reedux's intro page:
We released a new library that helps modulating a Redux app and allows dynamically adding/removing Reducers and middlewares.
Please take a look at https://github.com/Microsoft/redux-dynamic-modules
Modules provide the following benefits:
Modules can be easily re-used across the application, or between multiple similar applications.
Components declare the modules needed by them and redux-dynamic-modules ensures that the module is loaded for the component.
Features
Example Scenarios
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With