Hooks 11 exercises
solution

Add types for the useReducer Hook

Let's start by typing the state.

Typing the State

This is a good place to start because we know that state has a single property count that is a number:


const reducer = (
state: {
count: number;
},
action: unknown
) => {
...

With this change we've got some stuf

Loading solution

Transcript

0:00 Let's get cracking with a solution. This state, I think is where I want to start. This is going to be pretty easy to type because we know that it's only got one property, this count, and that it's going to be a number. Count is going to be a number. That's looking good already. We've got some stuff working, although a lot of this is still not erroring properly.

0:19 Now we need to type this action. We know that action.type is going to be a string. Let's start there. Then action.add or action.subtract. Let's try with an add number, and then a subtract number, not a subtraction, and number. This looks pretty good. Although, we'd need to add some...Add is possibly undefined. That's an issue.

0:47 We're still not getting the types right down here. Fortunately, the state is looking good. This state, count, number is all working. We're getting the right inference down there. State is looking good, but dispatch is not looking good. This is actually a really bad inaccurate type. We can make it a little bit better by saying "Add or subtract" here.

1:11 Why do I keep want to write subtraction? That's not good. Now, one of our cases is failing and failing correctly. We have add and subtract here working nicely now. This isn't good, though, because it basically allows this to be passed into our action, which should not be allowed. You can think of this as there being two cases where we want to pass something into our reducer.

1:38 When you have those cases, you should be thinking of a union type, two different possibilities of different objects with different shapes that can be passed in. TypeScript is really good at handling this. We're going to redeclare this. We're going to say the first type of action that you can pass in is one with a type of add.

1:57 That has an add number on it. This first case is totally working. Actually, weirdly, everything is feeling good, although these errors are pretty not going to be what we expect because we're not handling this subtract case. How do we syntactically add that to the union? We add it to the union type. We say type subtract, and then we say subtract is going to be a number.

2:24 Now, everything works beautifully. Inside the reducer, we're getting all of the proper inference. Before the switch case, we would say action.add or subtract is the type. We can't access add or subtract because we haven't actually gone in and checked which one is which. Inside these switch statements, we know that action.add is only going to be of type add because we've done some narrowing there.

2:50 This is how TypeScript works with discriminated unions. It's just so nice. Down the bottom here, we're going to get proper errors if we try to call this in the wrong way. If we pass type subtract, this is going to yell at us because you can't pass property add to this type. Really, really nice.

3:09 The other ways that you can do this is basically just different ways of organizing these types to make them look nice. This is a choose-your-poison situation. I would probably draw these out of the function themselves. I would usually put them as reducer state like this and reducer action just to make it a little bit easier to organize in case you need to...because sometimes you need to use these types.

3:32 Exporting them from the file is really useful. Another way you can potentially type this is the third solution by using the built-in reducer type from React itself and typing the reducer on the function level. Now you have reducer where you can basically say the first argument is the state and the second argument is the action.

3:52 You can see that if we go into this type definition, we have S and A here where we have the previous state, S. Then action, A, and then returns S, like states, basically. Those are three different ways that you can do it. UseReducer, if we command-click and take a quick look, there's only one function overload.

4:10 It's relatively simple. We take in the reducer, which is -- extends the reducer type that we've already seen. It has to match that. We take in the reducer here. Then we have an initial state and an initializer, which looks like it's just legacy, actually, because this only can be passed in as undefined.

4:27 Then you end up with the state, add a dispatch function to dispatch the things that you get from this reducer up the top here. Hello, Matt from the Future here. I just wanted to correct something that I said. UseReducer here, it actually has five overloads, not one overload, like I said.

4:44 These five overloads, you're probably not going to need to know what they are. They're just for when dispatch has fewer arguments than you expect or something. The outward API useReducer is going to be exactly the same, like I talked about in the solution.