I have a reducer tree that looks like this:
module.exports = combineReducers({
routing: routeReducer,
app: combineReducers({
setup: combineReducers({
sets,
boosters
}),
servers: combineReducers({
servers
})
})
});
Now the setup
key holds a form that needs to be reset once we've submitted it. However I have no way to access the entire setup
tree because using combineReducers means the reducers only manipulate the data at the leaf nodes of the tree (sets
and boosters
in this case).
My first impulse is to make a function that reduces the entire setup tree like this:
function setup(state, action){
//If there's an action that affects this whole tree, handle it
switch(action.type){
case "FORM_SUBMIT": //DO STUFF
break;
}
//Otherwise just let the reducers care about their own data
return combineReducers({
sets,
boosters
})(state);
}
But that doesn't work, and also messes up the nice tree structure of my first code example.
Is there a better solution for this with redux?
combineReducers
is a nice pattern because it tends to enforce the idea that the reducers should be scoped to non-overlapping subsets of the store, decoupled from the structure of the store itself. It takes the opinion that you should be reducing the leaves, not the branches, and it handles the reduction of the branches.That said, there may be good reasons to use an alternative pattern. As I mentioned in a slightly related question, you can opt out of purely using
combineReducers
and decompose your reducers as you see fit.In your case, you could decorate your inner
combineReducers
:Here,
setupReducer
is a higher-order function. This can be tricky to reason about, but here's how I approach it:setupReducer
takes a reducer as an argument, because we're passing the result ofcombineReducers
to it.combineReducers
is(state, action) => state
.setupReducer
has to return a reducer, which, again, is a function of the signature(state, action) => state
.In other words it takes a reducer, and returns a reducer:
((state, action) => state) => ((state, action) => state)
. So it might look like:I kept your logic flow above, but as a point of caution, you may want to call
subReducer
unconditionally and then modify its output. Otherwise you'll have to make sure your branches where it's not called always produce an object of the same shape, which seems like a potential sticky point of coupling.@acjay's answer is a great idea! I just wanted to use the old method
reducer(state, action)
and not a higher-order function. So I created a method that composes reducer in a master-slave relation.MasterSlave
Miguel's code would be then used in this manner: