Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reducing an entire subtree with redux combineReducers

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?

like image 325
Migwell Avatar asked Dec 23 '15 02:12

Migwell


People also ask

What does combineReducers do in Redux?

The combineReducers helper function turns an object whose values are different reducing functions into a single reducing function you can pass to createStore .

How do you use multiple reducers in Redux?

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.

Does Redux call all reducers?

One frequently asked question is whether Redux "calls all reducers" when dispatching an action. Since there really is only one root reducer function, the default answer is "no, it does not".


1 Answers

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:

module.exports = combineReducers({
    routing: routeReducer,
    app: combineReducers({
        setup: setupReducer(
            combineReducers({
                sets,
                boosters
            })
        ),
        servers: combineReducers({
            servers
        })
    })
});

Here, setupReducer is a higher-order function. This can be tricky to reason about, but here's how I approach it:

  • We know setupReducer takes a reducer as an argument, because we're passing the result of combineReducers to it.
  • We know that the signature of the reducer returned by combineReducers is (state, action) => state.
  • We also know 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:

function setupReducer(subReducer) {
    return (state, action) => {
        //If there's an action that affects this whole tree, handle it
        switch(action.type){
            case "FORM_SUBMIT": 
                // ... create newState
                return newState;
            default:
                return subReducer(state, action);
        }
    }
}

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.

like image 114
acjay Avatar answered Oct 27 '22 10:10

acjay