Reputation: 7374
I have generated a project using Create React App and then added Redux.
The redux state is then split into three parts that each has its own reducer and some middleware defined. The reducers are place in files called part1.js
part2.js
part3.js
there is then a common.js
file that imports the reducer and the middleware from part1-2-3.js
and adds them to combineReducer
and applyMiddeware
.
My question is if there is anyway to not having to import everything in one place. What I want is to be able to add the reducer and middeware to comineReducer
and applyMiddleware
from within part1-2-3.js
, the reason is to get rid of an explicit common boilerplate code file in common.js
. Is this possible or is the only way to import everything into one place?
UPDATE
I have now great examples on how to solve the combineReducer
part, however I still need to do something similar for applyMiddleware
. I have found an example from the following repo on how to do something similar with applyMiddleware
. However its in TypeScript and I have a hard time translating it into what is the minimal way to get this working within a JS React/Redux application. Would be great with some examples.
UPDATE
So I finally found this minimal library doing what I want.
Upvotes: 0
Views: 1101
Reputation: 1822
Yes you can. We use it with dynamic import and works well. We use with react hooks.
use store.replaceReducer
https://redux.js.org/api/store#replacereducernextreducer
in configureStore (or any file when you call createStore
from redux)
const store = createStore(/*...*/)
add
store.injectedReducers = {};
// Reducer registry
and create a new file with injectReducer hook.
const useInjectReducer reducer => {
const store = useStore();
const key = Object.keys(reducer)[0];
if (
Reflect.has(store.injectedReducers, key) &&
store.injectedReducers[key] === reducer[key]
) {
return;
}
store.injectedReducers = {
...store.injectedReducers,
...reducer
};
store.replaceReducer(combineReducers(store.injectedReducers));
}
and you can use it in react App:
export const TodoPage = () => {
useInjectReducer({ [REDUCER_NAME]: TodoReducer });
/*...*/
}
if you use server side rendering you need to be sure redux not cleaning up the states for missing reducers before dynamic import. You can create a dummyReducers to prevent that.
const dummyReducers = Object.keys(initialState).reduce((acc, current) => {
acc[current] = (state = null) => state
return acc;
}, {});
and add this for:
const store = createStore(
combineReducers(dummyReducers),
initialState
)
We use the same pattern to Inject Sagas.
Upvotes: 1
Reputation: 55643
Yes! I have a reducer registry which is similar (almost identical) to this reducer manager: https://redux.js.org/recipes/code-splitting#using-a-reducer-manager:
const DEFAULT_REDUCER = state => state || null;
export class ReducerRegistry {
constructor() {
this._emitChange = null;
this._reducers = {};
}
getReducers() {
// default reducer so redux doesn't complain
// if no reducers have been registered on startup
if (!Object.keys(this._reducers).length) {
return { __: { reducer: DEFAULT_REDUCER } };
}
return { ...this._reducers };
}
register(name, reducer, options = {}) {
if (this._reducers.name && this._reducers.name !== reducer) {
throw new Error(`${name} has already been registered`);
}
this._reducers = { ...this._reducers, [name]: { reducer, options } };
if (this._emitChange) {
this._emitChange(this.getReducers());
}
}
setChangeListener(listener) {
this._emitChange = listener;
}
}
const reducerRegistry = new ReducerRegistry();
export default reducerRegistry;
Then I have my redux domains organized into folders like reducks-style: https://github.com/alexnm/re-ducks
In the index.js
of the reducks
domain, I import the reducer registry and register the reducer:
import Domain from './name';
import reducer from './reducer';
import { reducerRegistry } from '...wherever';
reducerRegistry.register(Domain, reducer); // register your reducer
export { ... whatever }
Finally, my store
uses the reducer registry like this:
export const store = createStore(
combine(reducerRegistry.getReducers()),
initialState,
composeEnhancers(applyMiddleware(...middlewares))
);
// Replace the reducer whenever a new reducer is registered (or unregistered).!!!!
// THIS IS THE MAGIC SAUCE!
reducerRegistry.setChangeListener(reducers =>
store.replaceReducer(combine(reducers))
);
export default store;
This setup has worked magically for us. Allows us to keep all of our redux logic very isolated from the rest of the application (and from other redux domain logic!), works fantastic for code-splitting. Highly recommend it.
Upvotes: 1