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 mirrorscreateReducer
’s syntax. More info here.slice
is whatautodux
uses to generate your action types
Now let’s add our first action: addTodo
.
autodux
feedsstate
andpayload
toaddTodo
, just like a reducer.- Using the spread operator,
addTodo
merges the currentstate
(list) with a new object containing propertiesid
,text
, andcomplete
. shortid
will provide uniqueid
s using itsgenerate
function.payload
is the todo text, which gets assigned to thetext
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.