All Articles

Autodux Is Awesome Let’s TDD Code a Todo List Duck

My last couple of posts were all about reducing the Redux boilerplate needed to create List ducks.

Eric Elliott’s Autodux library 10xed the idea and automated the entire process.

This

const reducer = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state;
  }
};

const actionTypes = {};
const actions = {};

Is now this

const counter = autodux({
  slice: 'counter',
  initial: 0,
  actions: {
    increment: (state) => state + 1,
    decrement: (state) => state - 1
  }
});

That’s it. Everything is here. counter is an object containing your reducer and action creators.

Let’s Build a Todo List Duck!

In case the terminology’s new, a Duck is an actions/reducer bundle. See Erik Rasmussen’s proposal here.

Our Duck will add, remove, and update todos. We’ll do this TDD (Test-Driven Development) style, so the unit tests come first.

Setup

All you need is autodux and a testing library. Even though it’s overkill, I’ll use my own React/Redux starter, which uses Jest for testing. Feel free to clone it here.

After you’ve initialized the project, do npm install autodux and npm install shortid. shortid will generate unique ids when creating todos.

Initial test

Since I’m using Jest, describe will contain a particular feature, in this case todos duck. Up top, I’ve imported todosDuck from my duck file.

Before writing the assertion, let’s discuss how todos will be added. You should be able to provide some text as your todo, and it’ll be properly turned into an object with id and completed properties.

todosDuck.actions.addTodo('Buy groceries')

returns…

{
    id: '123',
    text: 'Buy groceries',
    complete: false
}

id will be generated using shortid.generate() and complete will default to false.

So the test might look like

But the test fails

The issue is Cannot read property ‘addTodo’ of undefined.

That’s because we imported todosDuck and its .actions property does not exist, so calling todosDuck.actions.addTodo results in that error.

Let’s go define it!

We’ll import and invoke the autodux function. It takes an object describing your duck, including initial state, action creators, and reducer.

  • initial represents the reducer’s initial state: an empty array.
  • actions will contain functions describing how your reducer should respond to incoming actions. The syntax closely mirrors createReducer’s syntax. More info here.
  • slice is what autodux uses to generate your action types

Now let’s add our first action: addTodo.

  • autodux feeds state and payload to addTodo, just like a reducer.
  • Using the spread operator, addTodo merges the current state (list) with a new object containing properties id, text, and complete.
  • shortid will provide unique ids using its generate function.
  • payload is the todo text, which gets assigned to the text property.
  • complete defaults to false, as previously mentioned.

Let’s export the entire duck.

We’ll console.log it in the test file.

Here’s what it looks like.

We can see the initial state, reducer, and actions all set up for us.

And guess what? Our test passes!

This process in TDD, by the way, is called red-green-refactor. We write the test first, watch it fail, fix and repeat.

The next test

Our duck’s next piece of functionality is removing todos. It should find and remove using a unique id.

todosDuck.actions.removeTodo('123')

should remove…

{
    id: '123',
    text: 'Buy groceries',
    complete: false
}

Here’s the test.

If our array has a todo with id: '123', then passing removeTodo('123') to our reducer should find and remove that todo.

Here’s our result.

todosDuck.actions.removeTodo is undefined, so you can’t use it as a function. That’s our red, now let’s fix it and go green!

removeTodo takes an id (payload) and uses filter to keep whatever doesn’t match that id.

And our tests are now a beautiful shade of green.

The final test

Our last piece of functionality is updating a given todo. Just like removeTodo, we’ll use id to locate it, and it’ll be merged with whatever the new todo is.

So an action like this

todosDuck.actions.removeTodo({
  id: '123',
  complete: true
});

should find and update the .complete of…

{
  complete: false,
  id: '123',
  text: 'Buy groceries'
}

to

{
  complete: true,
  id: '123',
  text: 'Buy groceries'
}

Here’s the test

And the failing result

Nothing new here, updateTodo hasn’t been defined yet. Fixing time!

We’re locating the old todo by id, just like removeTodo. This time, however, if todo.id matches the given id, we’ll merge it with newTodo. This lets us update and/or replace a todo. Since newTodo is the second one being merged, its properties will override todo’s.

Let’s re-check our test.

3 out of 3 passing. We now have a fully functioning duck. Let’s look at our implementation code one more time.

Autodux has made the process so much easier. I’m excited to use it in future projects, and even current ones. It’s so easy to slip it into an existing project, because the end result is just actions/reducers. The export remains the same.