Use TypeScript to Type React useReducer

Share this video with your friends

Social Share Links

Send Tweet
Published 3 years ago
Updated 3 years ago

We get the state of our board from a reduce function. It currently has no error but also has no type safety since everything is inferred to be of type any. useReducer is generic and accepts type arguments, describing the state as well as the events it can expect to receive.

The simplest way to add types to a useReducer is to add them directly to the reducer function itself. We will do this by using an alias for our types.

Instructor: [0:02] We get the state of our board component from a reducer function. I don't have any errors with my reducer. When I peek at the values that are returned from it, I also see that I don't get any type safety, as all of these values are inferred as any. That's not particularly helpful.

[0:20] We can fix this in a few different ways. Like all the other hooks that return values from React, useReducer() is generic and accepts type arguments describing the state as well as the events that it can expect to receive. The simplest way to add types to useReducer() is by adding types to the reducer function itself.

[0:42] Let's jump to the definition for our reducer and start adding some types. The first argument of our reducer as well as its return value are going to be the same. They are going to represent the context or state, state-full context, whatever you want to call it, of the application itself. I'm going to create a new type.

[1:04] I'm going to call it Board Context. You might call it board state. In this case, I think context works pretty well. I'm going to go and create an interface for that and start setting up that type. We also need some type information for the event or the action that we send to our reducer.

[1:28] This reducer is going to follow a fairly common convention of sending an object with a type key, along with some other data, depending on the type of event we're dealing with.

[1:38] When we start to think about this object, this event object, we realize quickly that it actually is going to take a slightly different shape depending on what the type key is. We might start typing this by creating a simple interface. We can say, this is a board event.

[1:55] We might say that it takes a type that is of type string. It could also take some other optional properties, depending on what that type looks like. We might have a board config here. We might also have an index type number depending on the type. This technically works.

[2:15] We can go ahead and say the event is our board event, but it's not actually a strong as it could be. We know that a reducer should take a very specific set of types. We can have valid types of events and invalid types of event.

[2:31] Instead of just saying that our type is this string, we could actually use a union type here to express what types actually look like. This is a bit better because now we'll get some type Henson's autocomplete when we use the type key.

[2:45] We'll also get errors if we send an invalid event to our reducer. We can strengthen this even more by instead of simply using a union for our type property, we can use a discriminated union for the object itself.

[2:59] This is going to tell TypeScript exactly what the object should look like, based on each unique event type. Not only will we get errors and helpful type hints for the type key itself.

[3:11] Depending on which type we use, we'll also get help and type safety for some of the other values associated with that event as well. If we were starting from scratch, this also makes writing React's reducer function much easier.

[3:24] The compiler knows exactly what each event looks like when we switch on that type property. Now that we have our event type, and we've annotated everything, I'll jump back down to the component.

[3:35] Without adding any generic arguments to our hook, useReducer now correctly infers the type of both our return state, as well as its send function.

[3:43] If we take a look at these individual values, we'll see that they are correctly identified, instead of just being any, which is going to be super useful in the rest of our component. We can also take a look at the send function that we get back from useReducer and start typing it.

[3:58] We'll see as soon as I type the type key, I'm going to get some nice autocomplete that's going to help me out. I'm also going to know whether or not this object is complete because typescript will tell me. It'll tell me that we need a type. We need a value for board.

[4:14] I need to provide that and that board needs to match the specific type as well. To recap, I suggest typing your reducer functions directly and using discriminated unions to describe the events or actions that it will receive.

[4:27] This is going to provide all of the type safety that you need and it is going to improve the developer experience when using the reducer's return values in your component.