Transcript from the "Combine Reducers" Lesson
>> Okay, so at this point, we've got about 60% of the Redux API covered. We've got bindActionCreators, which kinda just uses compose. Compose takes multiple functions and creates new functions out of them. We got createStore, which is arguably the most important part of the API, and itself had four methods.
[00:00:20] So we're 60% of the way through, and if you think about actual complexity, we're really a lot closer to understanding all that there is to Redux. Applying it will bring up some new challenges that we'll have to work through, but Redux itself is just objects and functions. So let's look at combine reducers, and I kind of alluded to this earlier, and there was a question around this as well, as the state gets a little bit more complicated, changing this stuff can be a little bit harder as well.
[00:00:53] So go ahead, let's clear out some of this. Let's say we had an initialState where we had, I don't know, like a to do list where we might assign stuff to different users. So our initialState Could have users. And we'll give an id of 1. And the id of 2.
[00:01:21] We'll give them name. Can't see the names on the zoom from here. I can see Eric. Yep, that's an object as well, you gotta wrap these into an object. Great, and then we'll have tasks. Which will have, I don't know, a title. And we'll say Order more monster energy drinks.
[00:01:58] They're not sponsoring, so they don't get mentioned. Order more energy drinks. All right, and as we saw before, if we just wanted to change user number 2's name, we would have to start dropping through this object spreading as we went along. This could get a little bit more complicated.
[00:02:14] One of the things that I recommended in that slide earlier was to go ahead and figure out can we make stuff flatter? I'll kind of just pull in what the kind of more complicated reducer would look like, and then we will simplify it a little bit as well.
[00:02:29] So let's pull it in, we'll kind of talk through it. We'll add these. So, if we look, we've got two action types, which is add a user and add a task. The point to payload is going to be title of the task, the name of the user. We didn't have to nest them objects per se, in fact, let's not.
[00:03:02] But you can see, as this object grows, we'd have to spread out the state and just the user array. But then, take everything else that's in the user array and just add the additional payload on there. You can imagine, we saw this in that slide before, this grows over time.
[00:03:16] This is a relatively simple application, and it's already getting tedious. When I showed that slide when we had the city being nested under location being nested under author, and we show the number of spreads, a bunch of your eyes got very wide, right? You're like do not want, absolutely not, right?
[00:03:36] And so, if you find yourself doing this, right, it is time to think about whether either A, as a question I saw before, which is do I need to change the shape of the state on the way in, or do I need to split this up between multiple reducers?
[00:03:52] And one way to think about that is what are the nouns in this case? Well, changing the shape of the API if it is one kind of thing, there are times for performance reasons, you might even want to split out one model as multiple reducers, if you're only changing, let's say, the title all the time, but the rest doesn't change, right?
[00:04:10] And you could do it for performance reasons. But this case, we have two distinct things that we're working with in this case, and it would be nice if we just kinda split these out into two smaller reducers. Now, if you saw before, createStore only takes one reducer, so this is all kind of like smoke and mirrors.
[00:04:29] Combine reducers just basically makes one reducer out of a bunch of small ones that we can eventually hand to to createStore. So how would we refactor this? Well, we could say that this seems like, the thing that we're doing with users, this is one distinct thing that we're doing with tasks.
[00:04:50] So what if we actually said, up here we'll say const userReducer, and that is gonna take state, which is gonna start out as initialState.users and some action. And this is why I like to use the initialState as the default prop to each reducer, because I can just pull in what I need rather than having one master initialState somewhere else.
[00:05:19] So you can imagine, as this grew, that object would get very big for that initialState. I'd like to just kind of break it up with the reducer that's using it. So we have the userReducer, which is gonna take the state, which is the users on that larger object, and some action.
[00:05:33] We can just pull this in, and we're gonna have to refactor it slightly. If I make a boo boo, someone notice. So we've got the ADD_USER, and now, really, this is just the actual users. Great, and we can do something similar here, so we don't wanna say dot users.
[00:05:59] One thing we could do here, since we know it's just users since this is just a variable, let's just make our lives easier and call it users. And for the taskRreducer, We'll say tasks initialState.tasks, and the action. And we'll pull in this code right her. And now, this is simply this array like that.
[00:06:38] And so, we've made each one of these reduces way simpler, and now we're gonna get effectively the same result by making what our reducer, we'll just say combineReducers, and we're gonna say that the users part of the tree is gonna use userReducer. And the tasks part of the tree is gonna use the taskReducer.
[00:07:08] These are all singular. So, if you think about the shape of the object in the initialState, right, we had users and tasks, right? And so, we're basically providing that same shape to what combineReducers gets. So instead of having one that took the entire object, it's gonna be like, cool, I'm gonna create a reducer that, for this section of the tree, for users, is gonna use a userReducer, and for tasks is gonna use the taskReducer, right?
[00:07:33] So you're basically defining what reducer should be used on this larger state tree and breaking it up into smaller pieces.
>> Nested like user dot. Yeah, it's basically you're just kinda mapping it to the shape of your state. It can be as nested as you need. Now, clearly, the further you nest, the more complicated it's going to get, so the advice that I would give in that sense is as much as you need to and as little as you can get away with, right?
[00:08:02] The flatter your object is, the easier it's going to be. Because imagine, could you continue to nest combineReducers? Yes, however, when you get that entire state object back, guess who's responsible now for navigating down that entire tree to get to the piece of data that they need, right?
[00:08:19] The flatter you can keep the object, the easier it is for both storing data as well as getting the data back out, right? It might make sense in your UI that maybe all this stuff is nested. Well, a lot of times, keeping that stuff as flat as you can get away with, nest as much as you need to, and as little as you can get away with.
[00:08:41] Great, so we've got a reducer here. We can just pass that in since they'll have their own. What's the issue here? Unexpected token.
>> I guess.
>> What is that? Yeah, perfect. So, now the store, if we wanna say console log define string during initialization. Reducer users returned undefined during initialization.
[00:09:32] If the state passes to the reducer is undefined, I broke one of my cardinal rules from before. Does anyone see what rule I broke? Yeah?
>> You're not returning default. So when we started off, you said that by default I'm just gonna return the state that gets passed to it.
[00:09:54] You're not returning the default state-
>> Yep, exactly.
>> So the if statement was not being run, you're not returning.
>> Exactly, exactly, right? In the case that we don't hit the conditional, this function returns undefined, so we need to make sure that we return, in this case, users.
[00:10:12] And in the other case, tasks. Cool, and then I gotta finish typing the word console.log. And this one will do store.getState. And you can see that I have the same basic shape to my state as I had before, even though I split it up, right? And so now, the really interesting thing about this, and one of the things I really love about the way that the Redux implementation works, is all of the actions whether they are ADD_TASK or ADD_USER will flow through every combined reducer.
[00:10:53] So for instance, if it's like add task to user, both of those smaller reducers will get the action so they can both kinda handle it separately. You might have take an assigned to property on the task, and update it to point towards the user. You might take the task ID and put it on an array of tasks for the user as well, right?
[00:11:12] Every action flows through every single reducer, which means to do a thing, you don't have to figure multiple actions to handle every single step that you need to do. You can fire one action and have that action flow through all of the combined reducers and be able to update the state accordingly.
[00:11:30] So it's very similar to having one giant reducer but you won't end up being very displeased with yourself as your application grows. Great.
>> What if same attaining the same property? Right?
>> So the question was what if different reducers change the same property? The great news is that they can't.
[00:11:59] Because if you look at the user reducer, it only has access to the users part of this state tree, right? And the tasks reducer only has access the task part of the state tree. They've basically taken our large state tree for our application and divided it up into a bunch of smaller pieces so that each one of these reducers handles its own little part of the tree.
[00:12:23] So two different users can't modify the same piece of state cuz they don't have access to it.