2.3 Combining Reducers
Using a single reducer will get unwieldy as our application grows. In this lesson, we'll split our reducer into multiple functions, and then use a Redux utility to combine them. This will make our application much easier to handle as it grows.
1.Introduction2 lessons, 04:39
2.Get Started With Redux6 lessons, 41:43
3.Create React Components8 lessons, 50:18
4.Application Structure9 lessons, 56:09
5.Implement the App9 lessons, 1:31:34
6.Conclusion1 lesson, 01:32
2.3 Combining Reducers
In the last lesson we, created our Redux store and we saw how we can use a reducer function to convert an action into a state change. Now if you're thinking ahead, you probably realize that eventually we are going to have a lot of different actions that our reducer needs to be ready to respond to. If we have just a single reducer function, as we do right now, this is going to get to be a pretty big, bloated function that will be a little bit difficult to work with. So, Redux gives us a way to combine multiple reducer functions into one main reducer. So, let's see how we can do this. Now before we jump into this, something we did not talk about at the beginning of our previous lesson was thinking about your state shape. Now, spoiler alert here. I've already completely built the application that I am showing you how to build right now. In my mind, I already know what our state is going to look like as our application grows. However, of course, you don't know that yet. And when you're building your own applications, that's not something you may think about right away. However, it's important to know the general shape of your state as you're building your application. In fact, it's a good idea to, as much as possible, think about your state shape before you actually begin building your application. Now, you can think about your state shape just as a Java Script object because that's, of course, how it's gonna be represented. So right now we know that our state is going to have a card array, right? We've already created part of a reducer for that. It may also have a decks array, right? It will have an array of decks to manage those cards. There may be other values of well such as a SelectedDeckId. Right. Or maybe a study mode is on or off, right, that could be true or false. Now to be fair here, selectedDeck and studyMode are both things that will eventually be managed by the rotor because we're going to have those tie into the road that's currently showing in the URL. However, even then we're going to use a package that will tie our rotor to Redux so that those values will be stored in our state. However, for now let's just focus on cards and decks. These are two top level properties that we're going to have in our state and generally, it is recommended to have as many top level properties as possible. You might think that we would want to have individual deck objects with the cards array inside them. However, I've found that it actually is a lot easier if we have individual cards that have a deck ID property. Now the cards that we're making right now with I card don't have a deck ID but that's okay. We're going to add that later and it's actually going to be part of action.data. So we're not actually going to have to change the reducer too much from what we already have now. Anyway, the whole point of all this is to say that you're gonna want to have a state object that is as flat as you can have, meaning don't have a lot of nested objects inside of a raise. It's easier to refer to things by IDs and stuff like that. Once we have a flat structure like this, in our case, we have a cards property and a decks property. What we can do is break up our reducers so that each one of our top level state properties has a reducer of its own. Let me show you what this means. First, let's break out a card's reducer. So, let's create our new const here and it's going to be cards and this is just going to be a function that takes a state and an action. So, it's just going to be a regular function and this plain old function here is going to be a reducer in the same way that the function that we pass to create store is. In fact, I'm just going to copy everything out of this and paste it up here. The only difference here is that the state object that we have here is not actually gonna be the entire state tree that it was in our top-level reducer. Instead, it's only going to be the current value of the cards property. And so basically, we're breaking this state object into individual properties. So if this state here is just our individual cards array, then what we need to do is change what we return. So down here at the bottom of the ADD_CARD case after we create our card, we are returning a new object here, right? But the card's property is just an array, and so we don't actually need to return this whole object. Instead, we can just return state.concat, and we'll concat on an array with our new card in it. Because remember, state here is just the card's property, and it's an array. So we can get rid of all this. Now when we're splitting up our reducers like this, it's even more important to have a sensible default because all of our actions are still going to go through all of our different reducers. So as you might imagine, we will eventually have an added Deck action and of course we're going to have a Decks reducer or sub reducer, if you will, that will manage the ADD Deck action. However, the ADD_DECK action will also go through this reducer. So because of this, we want to make sure we have a sensible default. So again, it's going to look very similar to what we had previously. If we have a current state for cards, well, we would just want to return that, right? Because we don't want to change the state for the cards if it's not a card-specific action. Otherwise, we want to return an empty array because that is the default value of the card's property. All right, so now that we have this cards reducer, how do things change within our react to create store of call here? Well for now, let me just delete this whole switch statement. And if we want to do this manually, what we could do is this. From here, we can return our new state object and we could just call each one of our reducers manually. So we can see the new cards property is going to be the value of whatever is returned from our cards function and we pass it state.cards and we pass it the action. And so we pass it just the part of the state that this function is interested in. We pass at the action and whatever value returns is going to be the cards property of our new state. And, of course, this will work with all our reducers. So when eventually we have a decks property here and we've created a decks reducer, we would just pass it state.decks. We pass it the action and whatever is returned from the decks reducer becomes our new deck property. Now because this is such a common pattern, Redux gives us a helper function for this. So instead of passing a reducer function to create store, we can instead pass it Redux.combineReducers. And this is another function that takes an object and this object has a bunch of keys, of course. And the keys are the same keys that will be the top level values of our state object. So we want our state object to have a card's key, and so we're going to give it a reducer for that card's key. And that's going to be cards. And so if you compare this to what we wrote down here, the first cards here, that is the key name is the same as the key name that we created when we returned our object here. Then the second value here cards is of course a reference to the cards function, which is the same as are second cards here. And then, of course, where we did state.cards down here, this is referring to the old value of what our new state object.card is going to be replacing. So, of course then, this key will also be used to get the value of the old state. So basically, all we've done here is a bunch of refactoring. If we clean this up a little bit, we'll see that this actually works in exactly the same way as it did previously. If we go back to the browser here, you can see that we still have two lines being logged to the console. The first where the cards array has one item, and the second where the cards array has two items. So this is the way that we can create smaller, more organized reducers in our Redux application. Now as our state object grows and the number of actions that we have grows alongside that, we can create a new reducer for each new property in our state object and we can add actions into the appropriate reducers, and have smaller more focused reducers instead of having one monolithic function. There is actually one more small thing we can do courtesy of ECMA script six and that is inside of this combined reducers call because the property key and the property value are the same, we can take out the second one and just say cards and that should still work. Let's see if this refreshes. Yep, that's being compiled just as we would expect. And by the way, if at any point you want to see what code babble fi is giving us here, we can just pop open the public/bundle.JS file. And this right here is what is being generated for us. At this point, it's pretty much the same. We're not using a whole lot of special EDX six features. Pretty much the only things we've got is the arrow function here. And then also you can see right here on line 19, our shorter syntax is being converted to the longer syntax here. But otherwise, there's not really any need for us to look at that file. So that's great. Now as our application grows, we can add action types and add reducers in an organized and manageable way. So in the next lesson, we are going to build our very first React component.