1. React's Best Types and JSX
Today I'm going to be talking about React's best types. React and Types React are actually one thing, united. The major version releases are all synced together. Types React is very, very stable. Types are part of your API and your framework. Let's start by talking about the type of JSX. It's a React.jsx.element or JSX.element.
What's up friends, my name is Matt Pocock. I'm a full-time TypeScript educator. I'm pretty gutted that I can't be in Berlin to actually see you guys and say hello and stuff, but stuff. That means I've got to be here in the UK, but you can find my stuff at tuttletypescript.com, go and support me, and, yeah, I'm excited to give this talk.
Today I'm going to be talking about React's best types. And you might think that's a kind of weird framing here, right? Because React itself doesn't ship its own types. It sort of just basically says, okay, you can install React here, and then, as a separate thing, we won't ship any types to you directly, but if you install Types React as well, then we're going to give you some types with it. And you might think, because of that, that the React team is sort of not really involved in the types. So you might think, sure, the community handles the Types React, and the React team handles React itself. But actually, it's more like this. It's more like the React team has stewardship over both of them. And actually, there are members of the React team that really just focus on the types and make sure that the types are correct. So you might think, sure, okay, the React and Types React, they're still separate things. But it's better to think of them as actually one thing, united. And that's because a lot of the decisions they make are synced together. The major version releases are all synced together. So if you have 17 on React, then Types React is also going to be 17, 18, etc. They also do ship patches. So Types React might ship a patch change without React shipping a change, but this means that they're really tied together. So Types React is very, very stable because really they consider any major change to the types to be something that React also needs a major version change for. So while React doesn't actually ship any types, it oversees its types very, very closely, and a lot of this is due to the really good work of Seb Silberman and a bunch of others. So it's important to think about the fact that types are features and types are part of your API. Types are part of your framework. And if you don't understand the types that come with React, you're not going to have a very good time using React with TypeScript. So this is my mission today is to teach you the most important types that React exposes and how you can use them to better power your apps and sort of just find your way around in React apps.
Let's start by talking about the type of JSX. If you have just a node up here, for instance, like this div, then what type does TypeScript infer this as? Well, if we hover over this node here, we can see that it's a React.jsx.element. You can also type this as just JSX.element. This is actually a relatively recent change. They've moved a lot of the global stuff that used to just be in JSX.element into a React namespace.
2. React JSX and React.reactnode
So JSX.element represents a node of JSX, which can be a div with multiple div children. However, there are other things in React that can be rendered, such as strings, numbers, undefined, or null. These cannot be assigned to React.jsx.element, so the correct type to use is React.reactnode. React.reactnode represents all possible things that can be returned from a React component, including JSX, strings, numbers, and more.
So if you're using another project like solid or something in the same TypeScript TS config, it doesn't conflict. So JSX.element represents basically a node of JSX. It doesn't matter how many things are in that JSX node. So this could be a div with many other div children. It's always just React.jsx.element.
There's another type that goes along with this too, which is React.ReactElements, which is absolutely identical. In the React world, they both mean the same thing. And you might think that's really good. Now I understand every time I need to let's say type some children or something like that, I can use React.jsx.element and I'm all good to go. Except though, that there are lots of other things in React that you can render. It's not just like elements. You can render strings, you can render numbers, you can render undefined, or you can render null. All of these things are available to be returned from React components. And you can see by the number of errors here that these are basically not assignable to React.jsx.element. So you've probably seen at some point in your React career, type string is not assignable to type element or something along those lines. So in these situations, what is the correct type to use instead of React.jsx.element? Well, it's React.reactnode. React.reactnode, if we take a look at it, it contains string, number, boolean, React element, like a bunch of other stuff in here, React portal, null, or undefined. So it represents all of the possible things that you can return from a React component. This means if you need to type like a slot that can receive some JSX, like or a string or a number or anything that can be rendered to the DOM, then React.reactnode is the type that you need. Also 99% of the time, this is the type that you're going to want. I would actually just mostly ignore the fact that these types exist, React.jsx.element and basically just think of it as like, okay, we have some JSX here, what type does TypeScript need to give it? That's the type that it's gonna be. But in terms of actually using this, then actually like assigning types, actually using this within your application code, React.reactnode is the one that you are gonna need for using to represent JSX in all of its different forms.
3. React.fc and React.reactnode
Now you might not have heard of React.reactnode before, but you probably will have heard of React.fc. It represents a functional component. React.fc makes sure that the thing you're returning is JSX and provides default props, display name, and other features. React.fc has changed over the years, becoming safer and no longer allowing children to be parsed into the component.
Now you might not have heard of React.reactnode before, but you probably will have heard of React.fc. This is one of the most famous types in really React and TypeScript, and it's also relatively controversial. A lot of people have strong opinions about this type.
It represents a functional component. You can either say React.fc or React.function component, because that's what it's short for. And you can basically use it in a few different ways. If you don't pass it any type arguments here, then it's a function without props, or a component without props. But you can pass an object, and that object becomes the prop. So you can see here I'm saying children is React.reactnode, and then the children appear inside here. If I were to remove this, then I would get an error here saying, property children does not exist on type empty objects.
And React.fc gives you a couple of different things. It gives you first of all, it makes sure that the thing that you're returning is JSX. So it forces you to return something. It says void is not assignable to type React node. Hopefully you understand what that means. And it means that you get default props on here, so you can actually assign some sort of default props to it if you want. But I think this API might be slightly deprecated, lightly deprecated. And display name is also something that comes along with this, too. So React.fc, it's kind of, it forces you to basically assign the type on the function itself, which we'll get to in a minute. And it also gives you a couple of things like display name and default props. React.fc has changed over the years. It used to, by default, allow you to parse in children to it. I can't remember which version this was, it was either React or 16 or 17, they changed this. And really like now, React.fc is a lot safer than it used to be. Because what used to happen is you would have a component here that never accepted any children, but it would an error here. Where now it says type children string has no properties in common with type intrinsic attributes. Basically saying you can't parse children into this slot. We can see here too how tied in the types of React are to the major releases. It actually took a major release of React to get this changed in React.fc. Because if you think about it, it's a pretty big breaking change to go from one version of React.fc, which just implicitly had children in it, to a version which doesn't.
4. React.fc and React.jsx.intrinsicelements
React.fc used to return React.reactElement, but now it returns ReactNode. This means you can return anything you want from it. React.fc is a nice way to type your components. React.jsx.intrinsicelements is a type that contains all the different elements you can render into the DOM, with their respective props.
Another old weakness of React.fc is that it used to return React.reactElement. So if you used React.fc on this component, you would get an error saying that number is not assignable to ReactElement or JSXElementConstructor. Another very familiar error to those who worked with React.fc. But now React.fc actually returns ReactNode. So if we go and look at it here, we can see it's a function component that has a call signature that returns a ReactNode. Now we can return whatever we want from it. This means that all of the old complaints about React.fc no longer apply. It returns a ReactNode so you can return anything you want from it and no longer implicitly adds children in there either. So React.fc is actually a pretty nice way to type your components if you want to. For me though, personally, I just prefer to type the props object. I don't know why, I think this is just really muscle memory. It's a little easier further down the line if you want to change it into components, although that's relatively rare. For me I just prefer this syntax to this syntax where you add React.fc and you add the props in there. For some reason this just makes me feel warm and fuzzy. But if this one makes you feel more warm and fuzzy then I wouldn't remove this from a code base if I came across it. So, React.fc? Yep, it's fine to use. Go for it.
Next we're gonna talk about a global type in React called React.jsx.intrinsicelements. But the important thing to understand before this is that sometimes you want to extract all of the props from a certain native element. Up here we have some div props and this annotation here, or some might call it an incantation, is basically a way to get all of the div props, like the props that a div element receives, and just stick them in a type. This means that if you're creating your own native element wrapper then you can use div props and then extend it or do something with it. Here we're HTML attributes and then passing that a type argument of HTML div element. But this incantation can be pretty annoying because you've got to really like spool through a bunch of different stuff in order to get the one that you want. Often you're going to want a way to just auto complete your way to the right props type. And this is where this type called react.jsx.intrinsic elements comes in. This is basically a record of all of the different things. If I just go and look for interface intrinsic elements, extends global JSX intrinsic elements, extends GSX.intrinsic elements, you can see that on this huge great big interface, there are a big list of all of the elements that you can render into the DOM. So we have a, ABB, address, area, article aside, audio B. And what we have, they're the keys. And then on the values is we have the actual types that they receive, the props that they receive.
5. JSX Intrinsic Elements and react.component props
So what I can do is add something cool and specify that it requires an ID string. This gives you an idea of how GSX intrinsic elements work. Another way to grab div props is to use react.jsx.intrinsic elements. In some situations, you'll want a list of all possible elements, which can be achieved using the key of react.jsx.intrinsic elements. Understanding this type is crucial in a react and TypeScript code base. Another way to grab native props is using the react.component props type helper. This has a use case for grabbing types from uncontrolled components.
So what I can do is I can actually add like something cool like this and just say, this requires an ID string. And now when I go back, or I do want to save, but not for long, I will come back and change that later. Now I can say const component equals like this and I can say return something cool and now something cool I'm getting an error because it requires the type of ID string and I have to pass it. So that gives you an idea of how these GSX intrinsic elements work.
So another way for me to grab these div props would be to say, okay, react.jsx.intrinsic elements. And then I basically index into it and I say, I want the div one of this and you can see that I can auto complete my way to it. So I just say div and boom it's there. This means I get to do a lot less thinking than when I just had react or HTML elements, HTML development, cause I need to remember all of those. Whereas here I can just remember, okay, it's on intrinsic elements. I'll just go and grab it.
In some situations too you're going to want a list of all of the possible elements that you can render to the dom. There are certain situations where this comes up and there are certain times in the react types as well that you'll see this too. And a really nice way to do this is basically to say all the possible element types are key of react.jsx.intrinsic elements. What this means is we take that entire object that we saw earlier and we basically say, give me the keys of that as a union. And it ends up with, if I just go and look at all of these, oh my God, do this funny dance again, I end up with a ABBA all of these keys inside a big union. And so if I say const example is all possible elements real, then I get all of the lists of keys. Very, very cool.
So react.jsx.intrinsic elements is one of the kind of most core things that you're going to see in a react and TypeScript code base. And understanding this type also will help you understand some of the weird errors that come up too. For those folks who are more experienced with react and TypeScript, you might have been shouting at your screen saying, what is this guy talking about? Because there's another way that you can grab the native props of an element. And that is using the react.component props type helper. We've got our div props from react.html elements. We've got our also div props from react.jsx.intrinsic elements. And then we've got also also div props, which are react.component props div. If we pass a string in here, we're going to get again, autocomplete of all of the native components. And it's going to give us basically the same results as if we had react.jsx.intrinsic elements. React.component props has another use case as well, which is let's say that you've got a select box up here or select component that you don't control. It's got some types here that aren't actually exported along with it. And we want to grab those types and put them in some props here. Well, we can just say react.component props and then pass in type of select.
6. JSX Intrinsic Elements vs. Component Props
So JSX intrinsic elements are recommended over component props for better TypeScript performance. However, react.component props is useful for third-party components. Now, let's dive into a piece of code. We have input props extracted from react.jsx intrinsic elements and an input component that receives these props. The inputs are a record of different input components, such as text and number.
So it not only works with native elements, it also works with actual components. This is really cool and makes it just mega flexible. So you can basically just use it to say, okay, here's a component, give me the props from that component. Lovely.
The question remains though, like if you have a choice between these two, between JSX intrinsic elements and component props, which should you use? I would actually recommend using react.JSX intrinsic elements because basically this is something that's globally available anyway, and it's instead of passing it to a relatively complex type helper, if we go look at this, you can see it's actually doing some sort of crazy conditional type in here. So instead of doing that, you're just basically accessing something on an object that already exists in a global scope. So if you're worried about TypeScript performance, which is kind of like something that you probably should be thinking about a little bit if you're doing right in the weeds with TypeScript, then this is going to be the better solution because it's actually just simpler for TypeScript to process.
But react.component props. If you just stick with this new code base, then fair enough. It's nice to just have one way of doing things that's really, really simple. And it's mega useful if you have those third party components that you are not in control of, but you want the props from.
Let's jump next to a big piece of code here. I'm going to explain this piece by piece. First of all, we have some input props at the top, which we're extracting from react.jsx intrinsic elements. Then we have an input component, which is a react.fc being passed the input props. So an input component, that's going to be like a function that returns something can pass in some props, which are the input props. So like onChange and name and all that sort of stuff. Now, ignore this class for now. Let's go down to these inputs. These inputs are a record of string. So where the key string, and then the value is input component. So we have basically we have a text input here, which is saying input type text. And then we have a number down the bottom here, which is my number component. My number component is a class. It's a react class component that takes in React.or rather extends React.or component passing in the input props. So inside these input props, you can actually pass in the props. So if we look at this dot props, it's going to say it is basically the input props here. That's a big old readout there. Now, we understand the whole thing. We understand that this record is so these inputs are supposed to be a record of different input components.
7. React.componentType and Passing Components
The correct type for this instead of fc is actually component type. React.componentType is a type helper that represents any React components, whether they are component classes or function components. This solves our error and allows us to pass both the MyNumber component and the text component into it.
The idea is we can say inputs.infact I'm going to move this to be a satisfies instead just so I can get autocomplete. Satisfies this. So inputs.number and text. We can see here that if we render these out and say inputs.number, then I can pass an on change to it like this and expect this to be correct for an input component. The issue we're getting, though, is that this number is saying type of my number component is not assignable to input component. That's because our type up here, React.fc is only really saying this can be a functional component. And classes are not functional components, they're something slightly different. So instead of React.fc, we want to be able to say any component can be passed into this slot and the correct type for this instead of fc is actually component type. So React.componentType is a type helper where if we take a look at it, it basically says either a component class or a function component. Just a lovely, nice, clean union between the two. And this P that's in this component type is representing the props of that. So it's a component class that receives these props or a function component that receives these props. And this solves our error. This means that text and number now, this MyNumber component, are both assignable to it. And if I changed this to component class instead, we'd end up with our React function components not being able to be passed to it. So React.componentType is a really nice way of representing any React components of the actual function, not the rendered JSX, that can be passed into a slot.
8. Passing Literal String to React.ComponentType
We can pass a literal string as a member of a set of inputs and use the React.ComponentType to handle it. React can handle passing in a function, class, or native tag. We have the ElementType type that ensures the native tag corresponds to the input props. This is exposed from React and can be useful when working with ASPROP and doing polymorphic stuff in React.
Okay, for our next one, we've added a new requirement onto the code that we saw earlier. We now have a set of inputs here. And the set of inputs now has a new member. It has a member called base. And that base, instead of passing an actual function to this, we're just passing in the literal string, input. And we want our type of React.ComponentType to handle that. Now that seems like really crazy, because we're kind of doing two different things here. We're saying okay, either you can pass in a function, or you can pass in a class, or you can pass in a native tag. And it turns out that like React will actually handle this perfectly. So if we say const... Let's just render this out. Input.Base. It will let us do this, right? So it just means sure base string, and we've got a key here, but actually we want to be able to say okay, when we have this, we need to make sure that it's basically a native tag that can accept the props of input props. So are we shooting for the moon, or do we actually have a type that can help us? Yes we do. We have ElementType. And now all of these actually work. So basically, you can basically pass in input, but we can't pass in some sort of random tag in here. We can't pass in this, because it's got to be one of the JSX intrinsic elements that corresponds to the input props. I mean that's wild, right? That's wild that this works and that this is exposed from React. If we actually take a closer look at it here, so, here we have only tags that can receive SRC, and we're passing in basically SRC's string into Reactor ElementType. So if we hover over this you can see that we have AudioEmbedIframeImageInputScript, and then we also have a React.ComponentType at the end, with SourceString on it. This is all of the native tags, but it's also a React.ComponentType added on the end. This can be really useful when you're wrangling with the ASPROP and you're doing polymorphic stuff in React, and it's just a cool type to have lying around and really interesting to see how it works.
9. Using react.element ref type
Let's imagine we're using an external library called React-Select and we want to pass a ref into that select. We specify the ref as HTMLSelectElement, but it gives us an error. To solve this, we can use react.element ref type of select to determine the correct type of ref for the select component.
I've got one more for you. Let's imagine that we're using an external library called, let's say, React-Select here, and we want to pass a ref into that select. And so we say, okay, the ref is going to be a HTMLSelectElement. That probably seems about right, but it's actually giving us an error here. And the error is pretty massive. HTMLSelectElement is missing the following types from type select. Oh god, oh god, oh god, it's really not having fun here. Wouldn't it be great if we could just say, okay, I want to know what type that select is from select. What we can do is we can say react.element ref type of select, and pass in select there. And now it's going to look at that select component, figure out what the type of ref is supposed to be and just put it in here.