Previously, I added some type information for the utility function we are going to look at. By clicking on the function, a lightbulb appears. We can click that and select Annotate everything with types from JSDoc
. Thanks to our plugin, it will type our function for us. It's not always perfect so double check to make sure it's exactly as you would like.
Along with the utility function, we are going to look at how to type a useMemo
hook, a useCallback
hook, a useRef
hook, a useState
hook, and a custom hook.
Instructor: [0:00] Let's jump into the board component file. We can start here by renaming the file to have a .tsx file extension, so that we can start writing our types. This component is a bit larger than our count display component.
[0:13] I expect, and it looks like I do see that we've got plenty of errors at this point, but no worries. We will start addressing these soon by chipping away and adding some types to our code. The first area that I see is coming from this create cells function in my reducer.
[0:26] I'm going to jump to that function definition so I can see what's going on. It looks like past me thought that it'd be helpful to add some JSDoc type information to this function, and present me is quite thankful for that.
[0:39] VS code has a really sick option in it's quick fix features that will annotate our function types based on that JSDoc block. If I click the create cells function, I will see that I've got this little light bulb icon. I can click on that and select "Annotate everything with types from JSDoc." Like magic, it's going to go through.
[1:00] Any JSDoc block that it sees above a function, it'll add type annotations for us, which is pretty awesome and saves us quite a bit of time. It's not always perfect. I'm going to need to set this mines argument as optional. Otherwise, it's pretty great and it gets us most of the way there.
[1:18] If I jump back up to my board component, I'll see that a lot of those errors are now gone because we get a lot of types from our utility functions in this file, which is really awesome. What about the values that we get from React hooks? How are those different?
[1:31] How are we going to get type information from those hooks? Let's jump in and look at a few examples. We take a look at this row array variable which comes from React's useMemo hook. This hook returns to us the value returned by its callback argument.
[1:47] We haven't added any types of that callback, so TypeScript does its best to infer the type as an array in this case of any value. We can be a little bit more specific here since this is an array specifically of all null values.
[2:00] It just pretty much use it as a placeholder array to loop through our UI. useMemo is a generic function that takes a type argument. This case, we'll pass it an argument which is an array of null values.
[2:16] Just below that, we have this getColumn array value which is going to be a function that we get from React's useCallback hook. useCallback returns to us the inner function, specifically a memoized version of the inner function argument instead of just this return value.
[2:33] We can get types here again by either passing an argument to the useCallback generic type argument space. Or we could simply type the inner argument itself by adding types to the function. Row index, in this case, is a number. This function is going to return to us an array of cells.
[2:51] Another React hook that returns a value is useRef. useRef is an object that has a current property that can persist between renders. That property has a value that can be inferred...its type can be inferred, rather, based on the initial argument that we pass to useRef.
[3:09] UseRef doesn't always have an initial argument. Sometimes, the type of value that the ref can hold could be of multiple different types. Sometimes, we might actually want to be explicit.
[3:20] In this case, the ref can point to either the initial value of null, or it can point to the DOM node that is rendered by this button element, or this button component, which is going to be an HTML button element, which we can type directly as the type argument to useRef. Now the ref type is correctly identified as a ref that holds a reference to the button element itself.
[3:51] Let's take a look at this useTimer custom hook that we have here. useTimer calls several hooks internally. One of those hooks is useState. Let's talk about useState first.
[4:02] We can type our state arguments, or we can not type it, in this case, by simply passing an initial value, which is, in this case, a number. Which works well for our purposes, because the time elapsed should always be a number, and the setter should always take a number as an argument.
[4:16] However, just like a ref can hold values with different types, so can state, in some cases. If we wanted to pass multiple types as a union type, we would have to be a little bit more explicit. If we wanted to do that, useState is a generic function, again, that takes the type of state that we're dealing with, in this case, a number.
[4:37] Another thing to note here is that the return type of our custom hook is this array. Specifically, this array holds a reference to the time elapsed and the reset function.
[4:48] This is a tuple, because we always have a set number of items in the array and they always appear in the same exact order. This looks a lot like useState. It's a pretty common pattern to see custom hooks return tuples, a lot like useState returns a tuple.
[5:01] Any time you're dealing with tuples, it's a good idea to be pretty explicit, because if you leave it up to TypeScript's inference, it's going to see our return value as simply an array of any particular length, with these different types of values. It doesn't know exactly which order those values might appear in.
[5:21] To explicitly type this hook, I'm going to set a return type of an array, specifically with a number in Position 1 and a void function in Position 2.
[5:32] We have a correctly typed custom hook with a tuple return type. To recap, all of the React hooks that return values are going to be generic functions that accept arguments to describe their return types.
[5:48] We have added types to values returned by useState, useRef, useMemo, and useCallback. We've also added some type safety to our custom hook that returns a tuple value. useReducer, we didn't talk about. It's a little bit more involved. We're going to tackle that one in the next lesson.
Hey Garrett, great feedback! Gonna re-post your Twitter message in case anyone else hits this:
Check your VS Code settings: If you see:
"typescript.validate.enable": false
Set that to true and you should be good!
If a '?' is added to the JSDoc param that we want to be optional before the types annotation for "mines" will match like this:
/**
* @param {{
* rows: number;
* columns: number;
* mines?: number;
* }} board
* @param {number[]} [mines]
* @returns {Cell[]}
*/
"typescript.validate.enable": false
this setting did not work. hit a roadblock with the "Annotate everything with types from JSDoc". I don't see the option.
hit a roadblock with the "Annotate everything with types from JSDoc". I don't see the option.