Redux: Writing a Todo List Reducer (Adding a Todo)

Dan Abramov
InstructorDan Abramov

Share this video with your friends

Send Tweet
Published 6 years ago
Updated 9 months ago

Learn how to implement adding a todo in a todo list application reducer.

[00:00] Just like in the previous two lessons, I'm using expect library to make test assertions and deep freeze library to prevent accidental mutations in my code. In this lesson, I will create the reducer for a to-do list application whose state is described an array of to-dos.

[00:18] Just to remind you what a reducer is, it's a pure function you write to implement the update logic of your application -- that is, how the next state is calculated given the current state and the action being dispatched.

[00:33] Before writing a reducer, I want to have a way of knowing whether its code is correct, so I'm starting by writing a test for it. I'm declaring two variables, the state before, which is an empty array, and the action being dispatched, which is an action describing user adding any to-do with some ID and a text.

[00:55] I am also declaring the state I expect to get after calling the reducer. Like state before, it is an array, but this time, it has a single element representing the to-do that was just added. So it has the same ID and the text as the action object. It also has an additional field called, "completed," that I want to be initialized to be false.

[01:20] We want to make sure that the reducer is a pure function, so I'm calling deep freeze both on the state and the action. Finally, I am ready to use the expect library to verify that if I call the to-do reducer with the state before and the action object, I'm going to get the result that is deeply equal to the state after I just declared.

[01:45] This concludes my first test. Now I can call it just like a regular JavaScript function. If it doesn't throw in the expect call, I'm going to see a message saying that the tests have passed.

[01:59] Of course, it fails because the reducer is not implemented yet. It's an empty function. So it returns undefined instead of the array with a single item that I expect in the test.

[02:13] To fix this, I would need my reducer to take a look at the action type property, which is a string. When it matches the at to-do string, which I specify as the action type in my test, to satisfy the test I need to return a new array which includes all items from the original array but also a new to-do item that has its ID and text copied from the action object and a completed field set to false.

[02:44] Finally, I add a default case to my switch statement because averages has to return the current state for any unknown action.

[02:54] Now the test runs successfully. Let's recap the data flow in this example to see why.

[03:03] First, I create the state array, which is an empty array, and the action object inside my test function. I'm passing them as arguments to my reducer function, called, "to-dos." The to-dos reducer accepts the state and the action as arguments and takes a look at the action type.

[03:26] In this case, the action type is a string saying, "at to-do," so it matches the switch case inside the reducer. The reducer returns a new array which contains the items from the old array and the new item representing the added to-do.

[03:44] However, the state we passed from the test was actually an empty array, so, at the end, we're going to get an array with a single item, which is the new to-do.

[03:57] Finally, we compare the return value to an array with a single to-do item to make sure that the reducer works as intended. The equality check passes. This makes the test successful.

jpbamberg1993
jpbamberg1993
~ 5 years ago

Why are you setting the default state of the reducer to an empty array? Why not an empty hash {}? since it is being placed within an array? and the array is supposed to be an array of objects?

dan entous
dan entous
~ 4 years ago

yes, the array represents a collection of todos, starting out as an empty collection.

Jason Tanner
Jason Tanner
~ 4 years ago

Is anyone else unable to run the plunkr here? It looks like the expect and deep-freeze javascript libraries are not providing either variables for use in our script.jsx file.

Christian
Christian
~ 4 years ago

Here's what adding a todo with a list reducer could look like in Immutable.js:

const { List, Record } = Immutable;

const Todo = Record({
  id: 0,
  text: "",
  completed: false
});

const todos = (state = List(), action) => {
  switch (action.type) {
    case "ADD_TODO":
      return state.push(
        Todo({
          text: action.text,
          id: action.id
        })
      );
    default:
      return state;
  }
};

const testAddTodo = () => {
  const stateBefore = List();
  const action = {
    type: "ADD_TODO",
    id: 0,
    text: "Learn Redux"
  };
  const stateAfter = List([
    Todo({
      text: "Learn Redux",
      id: 0,
      completed: false
    })
  ]);

  expect(todos(stateBefore, action)).toEqual(stateAfter);
};

testAddTodo();
console.log("All tests passed.");