Our Radio
component file contains several compound components to create a related group of radio inputs. The RadioGroupContext
is receiving a type of any
currently.
This is a great example to peer into how Strict mode works. To see the difference between the strict mode's true and false setting, we will set strict
to true
. This will change our RadioGroupContext
to be of type null
which will raise some issues later down the line where trying to destructure an object that might be null.
This also gives us the opportunity to better our application with error handling. I will write out a couple of custom hooks to help out with that.
Instructor: [0:00] Let's take a look at our radio component file. This file actually contains several compound components to create a related group of radio inputs. It uses React context to hold and provide state for its inner components.
[0:14] As always, I will start by renaming the file to have a .tsx extension so that we can write our types. For this file, I want to start with our context object right at the top. We initialize our context with null.
[0:28] Since we aren't in strict mode, TypeScript does not infer any type information from the initial argument of null. Our context gets an implied any type, which isn't super useful.
[0:39] This is actually a great time to illustrate some key differences between the strict modes true and false setting. I'm going to open up our TS config here. For the moment, I'm going to set strict to true so that we can see some differences here.
[0:55] Even if I am refactoring a project with strict set to false, I really love toggling it to true every once in a while just to get an idea of how much work I need to do to make my code work in strict mode.
[1:06] If it's not screaming at me too much, I like to go ahead and add all of the types I need before toggling strict back to false and moving on. If it compiles with strict is set to true, it should still compile when strict is set to false.
[1:19] It'll make our work much easier if we ever decide to make the rest of our code base compile in strict mode later. Jumping back to our radio context, I can now see that TypeScript has inferred our context type as null since that is the initial argument, which is not exactly right, because it should hold a value in its provider. I could change the default value to match what we pass to the provider, but I actually don't like that here.
[1:44] I want useContacts to return null if it's used outside of the context provider as these components are coupled by design and we want errors to happen as a expectation of that misusage. Instead, I'll pass an explicit type argument to React's create context function.
[2:03] I will say that this can have a type null, or a type of radio group context value, which I can then define here in the file. To save some time here, I've already got some type definitions for all of my components props at the very bottom of the file.
[2:21] I'll go ahead and add those all is well, just to get rid of some of the errors that aren't related to our context here. I've also got another context object here, the radio context, for individual radio buttons. I'll go ahead and type this much the same way I did my radio group context.
[2:49] All of our components and their context values are fully typed, but it does look like we still have some problems in our code. In each case where we now use our context, we are going to get some errors. That's because we are trying to destructure an object that might potentially be null if that context doesn't exist.
[3:09] In these cases, we probably actually want our app to throw an error because this means that someone is using the code wrong. If someone renders a radio input outside of the radio component, they should get an error.
[3:24] We can actually make these errors even better and more helpful to our future selves as well as other developers on our team. Typescript surfacing this issue gives us a great opportunity for better error handling of our code.
[3:37] Instead of calling useContext directly in my component, I'm actually going to create a couple of new custom hooks. In these hooks, I will get my context directly by calling React's useContext. I will check to see if our context is null.
[4:03] If that is the case, I will go ahead and throw my own error here with a more helpful error message. I think I'm going to give this hook a name argument, so that we can explicitly tell it which component it was called from, which makes debugging much easier.
[4:22] We can say that the name was rendered outside of a radio group component. We can tell the developer to wrap the name of this component with a radio group to get rid of this error. If we get through this error, we will go ahead and return our context.
[4:49] Because TypeScript narrows the type for us at this point, because throwing exits the function, we know that we always get a return value of our radio group context value here. Instead of calling React useContext in our components, we can use instead this custom hook.
[5:14] Since we have two of these things, let's go ahead and write a similar hook for our radio context. Now, when we call these hooks in our component, we know that the value can never be a null because we've already eliminated that possibility by throwing in our function. Our code is now more type-safe, and we've improved our app's error handling as well.
[5:59] I'll go back now to my TS config and remove the strict setting. Again, everything should work just as well when strict is set to false, which it should be in our refactoring project.
[6:12] To recap, if your context holds a different value in its initial argument than its provider, do not rely on type inference here. Type the value directly and pass it to createContext as its type argument.
[6:25] In usage, make sure that you're performing proper type checks on the values returned by React's useContext hook before assuming that you can use them safely. Writing a custom hook that handles these checks for you is a nice way to simplify its usage and improve your app's handling of TypeErrors.