TS Tricks: Type-safe Reducers

We're going to talk about how to build a type-safe reducer. Now, I use these for redux reducers, but these principles could apply to any function.

TS Tricks: Type-safe Reducers

If you enjoyed my post on type guards, this one might tickle your fancy. We're going to talk about how to build a type-safe reducer. Now, I use these for redux reducers, but these principles could apply to any function.

Let's start by typing some actions.

You'll notice we've created two action types MakeFriend and LearnSpell. Each of these has a type and a payload. The payload of each however is a different shape. In this case it's an object with a single key, but you can imagine a much more complex scenario.

Now at the bottom we have a type called WizardActions. This is what's called a discriminated union. What this means is that we can take an argument of type WizardActions and it will be exactly one the types in the union. Let's look at some more code to elaborate.

We now have a reducer that takes a state and an action and returns the current state. Don't worry, we'll handle these actions later. Now the action argument is of type WizardActions which means it's going to be of type MakeFriend or of type LearnSpell.

Let's expand on our reducer and keep going.

If you're used to redux, this should look familiar. We have a switch statement on the action type and we're handling each payload differently. For MakeFriend actions we append a friend to the list of friends. For LearnSpell actions we append a spell to the list of spells.

We actually don't need the default case because this is an exhaustive type-check, but it's still good to be defensive in case someone upstream uses an any. It's also necessary if we integrate this with redux.

How does this work? Well Typescript is pretty smart. It figures out on it's own that checking type will guarantee the payload is a certain shape. When we go to access that payload, it knows what properties to expect. If we attempted to check the spell of a MakeFriend action we would get a type error.

Let's have some fun now and call our reducer. First let's make some friends.

This reducer works how we expect at runtime; Harry meets Ron, then he meets Hermione. Let's learn some spells.

Now Harry learns Expelliarmus and Expecto Patronum!

If you want to know more about typing redux, I wrote an article about building a typed and code-split redux store.