Learn the fundamental pattern of building maintainable Redux applications: the reducer composition, and how it can be used to update items in an array.
[00:00] In the previous lesson we created a reducer that can handle two actions, adding a new to-do, and toggling an existing to-do. Right now, the code to update the to-do item or to create a new one is placed right inside of the to-dos reducer.
[00:17] This function is hard to understand because it makes us two different concerns, how the to-do's array is updated, and how individual to-dos are updated. This is not a problem unique to Redux. Any time a function does too many things, you want to extract other functions from it, and call them so that every function only addresses a single concern.
[00:41] In this case, I decided that creating and updating a to-do in response to an action is a separate operation, and needs to be handled by a separate function called to-do. As a matter of convention, I decided that it should also accept two arguments, the current trait and the action being dispatched, and it should return the next trait.
[01:05] But in this case, this trait refers to the individual to-do, and not to the least of to-dos. Finally, there is no magic in Redux to make it work. We extracted the to-do reducer from the to-dos reducer, so now we need to call it for every to-do, and assemble the results into an array.
[01:28] While this is not required in this particular example, I suggest that you always have the default case where you return the current trait to avoid all [inaudible] in the future. The part described in this lesson is pervasive in Redux's development, and is called reducer composition.
[02:05] This pattern can be applied many times, and while there is still a single top level reducer managing the state of your app, you will find it convenient to express it as many reducers call on each other, each contribution to a part of the applications trait tree.
It seems like when you separated the logic for
string and only handle the logic of creating the new state.
What duplication are you referring to specifically? I don’t see it in the end result. That said, you can definitely make functions even more granular if you’d like to.
The switch statement in the
todos functions have the same logic. Instead, if you create a
toggleTodo function that only handles returning the state for those actions, you can eliminate their concern with the
action. Of course, this is no longer composing reducers, but separating functionality into easier-to-read chunks.
I’m probably missing your point. Aside from having a
switch with the same constants, I don’t see any duplication in their logic. The
todos reducer handles changes in array, and
todo reducer handles changes to individual todo. There is no shared code between them, again, except for the
switch statement. As the logic gets more complicated (more actions are handled),
todo reducer will grow, but
todos will stay pretty much the same. That’s the point of separating them. If they’re not separated, adding more actions that change individual todos will require copy-pasting code for changing todo at an index—now that would be duplication. If you don’t agree please provide some code examples of the duplication you’re referring to (please mark duplicated logic with comments), and how you suggest to reduce that duplication.
It seems like when you separated the logic for
stringand only handle the logic of creating the new state.
When I'm removing duplication, I like to think of it in more functional terms. I don't want to have separate functions for action types, but instead want to provide pure functions that return the new data. I didn't see the logic duplication you are talking about in this case. You can clone JSBins easily and paste a link if you have some ideas for improvement. That'd be great!
At the end of the day, definitely remove duplicate logic with pure functions anytime it makes sense!
Here’s a different version, with
createReducer abstracted away. This removes the
switch statement, and I hope, makes it clear there is no duplication between
Thanks for the responses! I agree that the only real logic duplication is in the
switch statements. Here's an example of what I was envisioning: https://jsbin.com/rosuvobate/1/edit?html,js,console
Please let me know if this is acceptable within the best practices of Redux. Thanks!
This is acceptable, but when you add more actions that change a single todo (e.g.
EDIT_TODO) you’ll find yourself duplicating the logic to
map over the array and change a single item. :-)
There are no strict rules here. Do whatever makes sense. However I think you’re mistaken when you say
switch statements are duplication. In a large app, you’ll have a
switch statement inside every reducer, and some of them will handle the same actions. This is normal—not something to abstract away. With the approach you’re suggesting, you’re trading a visual duplication (
switch statements) for logical duplication (code to update a single item in an array). Anyway, it’s up to you. And this example is small enough that it’s hard to agree on a single best practice—it can be done in a variety of ways all of which are fine.
I see what you mean about the
map duplication when adding more actions. I definitely what to keep my code scalable and free from logical duplication.
Thanks again and great job on the videos!
Transcript says: you return the current trait to avoid all [inaudible 1:36] in the future Should be: you return the current state to avoid odd bugs in the future
i know this is probably 2 years late but why does passing undefined in the todo allow the test to pass ? is this because the creation of the reducer function that was abstracted doesn't know the initial state for the ADD_TODO action?
would it be wrong to pass an empty array into the todo function that gets called in the ADD_TODO?
thanks for your reply
@Patrick and anyone with the same question of "why do we pass
state argument in the context of the
todo() function refers to the state of a single todo item. In the case where we're adding a todo item (
'ADD_TODO'), there isn't a previously existing todo item to pass a previous state of, as we're only just creating it.
Therefor the reducer takes "nothing" as a state (represented by
undefined) and creates "something" as the new state (a new todo object).