Let’s refactor a birdbath.
My last article described how a higher-order List “Duck” might be written. Here’s the code in 4 screenshots.
Remember we prefixed our actions with the name
parameter. This helps prevent collision among other List Ducks.
Action Types
Action Creators
Reducer
Our higher-order Duck does its job well, but the reducer’s verbose! It could use some refactoring.
createReducer
createReducer
is a helper function from the Redux Docs. It lets you easily spawn reducers by passing initial state and an object describing how the reducer responds to a given action.
It looks like this:
It takes two parameters: initialState
and handlers
.
initialState
’s obvious, I hope — it’s our reducer’s initial state. 😏
handlers
is more interesting — it represents your reducer’s response to a given action.
This might look scary but let’s break it down.
Line 2 returns a new reducer. That’s right, it spits out a reducer, preloading its state
with your supplied initialState
.
Line 3 uses an if/else
to replace whatever switch
cases your reducer would’ve had. It checks if your handlers
object has the action’s type as a property.
If it does, invoke that property with state
and action
.
Else, just return state
.
Imagine we needed a counter
reducer. Most of us would write this:
This is perfectly fine. But what if we plug it into createReducer
?
Oh that is awesome! Concise, yet expressive. And it behaves the same way.
Refactoring our reducer from the last article may help us appreciate this even more.
Here’s the original reducer code, to recap:
The actionTypes.reset
case is the easiest to refactor, so let’s begin with that.
Recall that createReducer
passes the state
and action
to each of handlers
functions. We don’t care, though. If someone fires the reset action return an empty array. The previous state and action details don’t matter.
Let’s cut those out.
I’m using computed property names to dynamically set actionTypes.reset
as a key in our handlers
object. I wrote a post covering them here. Basically it evaluates actionTypes.reset
as ‘myListRESET’
because we originally created our Duck like so: makeListDuck('myList')
.
‘myListRESET’
then becomes a key on our handlers
object and we set its value to a function that returns initialState
. In the end, it looks like this:
{
myListRESET: () => initialState;
}
Up next, actionTypes.addOne
.
If someone fires the addOne
action, we expect an item
attached to it. We can also destructure the action to target its item
.
I personally prefer this but either syntax works.
Now we’ll write actionTypes.addMany
–same idea as addOne
.
We’re on a roll, let’s do removeOne
.
Remember from the last article, findItemById
takes an id
and returns a predicate comparing id
to item.id
.
For removeOne
, when this predicate returns true
(there’s a match), remove that item from the list.
In a similar spirit, here’s updateOne
.
When the predicate returns true
, return newItem
. Else, return the normal item
.
Last but not least, here’s set
.
Return whatever action.items
is. Easy-peasy!
I don’t know about you, but I think this reducer looks clean. And the functionality’s identical.