Enter Your Email Address to Watch This Lesson

Your link to unlock this lesson will be sent to this email address.

Unlock this lesson and all 828 of the free egghead.io lessons, plus get JavaScript content delivered directly to your inbox!



Existing egghead members will not see this. Sign in.

Just one more step!

Check your inbox for an email from us and click link to unlock your lesson.



Redux: Reducer Composition with Arrays

2:21 JavaScript lesson by

Learn the fundamental pattern of building maintainable Redux applications: the reducer composition, and how it can be used to update items in an array.

Get the Code Now
click to level up

egghead.io comment guidelines

Avatar
egghead.io

Learn the fundamental pattern of building maintainable Redux applications: the reducer composition, and how it can be used to update items in an array.

Avatar
Demetri

It seems like when you separated the logic for "ADD_TODO" and "TOGGLE_TODO", there was some duplication of logic with the switch statements. Wouldn't it be easier to have separate JavaScript functions for each action type? This way they wouldn't be concerned with the action string and only handle the logic of creating the new state.

Avatar
Dan Abramov

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.

In reply to Demetri
Avatar
Demetri

The switch statement in the todo and todos functions have the same logic. Instead, if you create a addTodo and 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.

In reply to Dan Abramov
Avatar
Dan Abramov

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.

In reply to Demetri
Avatar
Joel

It seems like when you separated the logic for "ADD_TODO" and "TOGGLE_TODO", there was some duplication of logic with the switch statements. Wouldn't it be easier to have separate JavaScript functions for each action type? This way they wouldn't be concerned with the action string and 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!

In reply to Demetri
Avatar
Dan Abramov

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 todos and todo:

https://jsbin.com/yezuramoxi/1/edit?js,console

In reply to Demetri
Avatar
Demetri

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!

Avatar
Dan Abramov

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.

In reply to Demetri
Avatar
Demetri

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!

In reply to Dan Abramov
Avatar
Abe Massry

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

In the previous lesson we created a reducer that can handle two actions, adding a new todo, and toggling an existing todo. Right now, the code to update the todo item or to create a new one is placed right inside of the todos reducer.

This function is hard to understand because it makes us two different concerns, how the todo's array is updated, and how individual todos 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.

const todo = (state, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        id: action.id,
        text: action.text,
        completed: false
      };
  }
};

In this case, I decided that creating and updating a todo in response to an action is a separate operation, and needs to be handled by a separate function called todo.

const todos = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        todo(undefined, action)
      ];
  }
}

As a matter of convention, I decided that it should also accept two arguments, the current state and the action being dispatched, and it should return the next state.

const todo = (state, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return { ... };
    case 'TOGGLE_TODO':
      if (todo.id !== action.id) {
        return state;
      }

      return {
        ...state,
        completed: !state.completed
      };
  }
};

But in this case, this state refers to the individual todo, and not to the list of todos. Finally, there is no magic in Redux to make it work. We extracted the todo reducer from the todos reducer, so now we need to call it for every todo, and assemble the results into an array.

switch (action.type) {
  case 'ADD_TODO':
    return { ... };
  case 'TOGGLE_TODO':
    return stat.map(t => todo(t, action));
  default:
    return state;
}

While this is not required in this particular example, I suggest that you always have the default case where you return the current state to avoid all problems in the future. The pattern described in this lesson is pervasive in Redux's development, and is called reducer composition.

const todo = (state, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return { ... };
    case 'TOGGLE_TODO':
      if (todo.id !== action.id) {
        return state;
      }

      return {
        ...state,
        completed: !state.completed
      };
    default:
      return state;
  }
};

Different reducers specify how different parts of the state tree are updated in response to actions. Reducers are also normal JavaScript functions, so they can call other reducers to delegate and abstract a way of handling of updates of some parts of this tree they manage.

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 state tree.



HEY, QUICK QUESTION!
Joel's Head
Why are we asking?