Redux Fundamentals: combining reducers

In part 1 of this series, we established what a reducer is. But a single function that takes two values and returns a new value isn't particularly powerful on its own. You can only manage one piece of state with it.

With one reducer, you could store its state in a single variable:

const isLoggedIn = isLoggedInReducer(wasLoggedIn, {
    action: 'LOGIN',
    username: 'joe',
    password: 'abcd',

And if you needed to track a second piece of state, perhaps the counter reducer from part 1, you'd have to add another variable. But if you had 10 or 50 things to keep track of, that could become hard to manage.

What if you could store all that state from your reducers in a single object that is shared by the entire application, and with a hierarchical structure that can be organized however you want? Turns out that's exactly what Redux's combineReducers function does!

combineReducers takes one argument: an object whose properties will also be the properties in our state object. The object's values are our reducer functions.

const parentReducer = combineReducers({
    isLoggedIn: isLoggedInReducer,
    counter: counter,

This creates a new reducer, taking state and action arguments. state is an object storing the combined state of all the reducers, and action is a normal action object. When the function is called, it calls each of the reducer functions, and returns an object with each reducer's return values:

const newState = parentReducer(
    { isLoggedIn: false, counter: 1 },
    { type: 'ADD', value: 2 });
console.log(newState); // { isLoggedIn: false, counter: 3 }

The best part? Because this new function parentReducer is also a reducer, we can pass it (and any other reducers) to combineReducers again and create a tree of state organized however makes the most sense to us.

const megaReducer = combineReducers({
    someState: parentReducer,
    username: usernameReducer,
    profileImage: profileImageReducer,

In short, combineReducers takes the tedium out of organizing lots of simple functions into a single tree-shaped object that manages all of your app's state, making sure all your reducers get called and their return values stored in the right branches of the tree, leaving you with the simple job of deciding how to organize your app's state data to best meet your needs.

But how do we create, update and listen for changes on this state object? Tune in next time!