TypeScript is fantastic to use with React because it can help us catch a lot of bugs we wouldn't catch otherwise. It helps our team have good documentation and it makes React easier to use (over the long term).
But there were a lot of things I didn't understand in the beginning that I wish someone would have explained to me.
In this post, I'm going to walk you through the basics of using TypeScript and React, with code.
We're going to look at:
how to create a new TypeScript and React project (it's built-in now!)
how to type props for a class component
how to type state for a class component
how to type props for a functional component
how to type state for a functional component (with hooks!)
The key idea is that we want to define the "shape" of our props and state with TypeScript types -- and TypeScript will ensure that everything conforms to the right "shape" in our code.
interface we're going to be using for props and state throughout this post:
If you're not familiar with TypeScript interfaces yet, the above code says that any object of the
CounterPropstype must have a
messagekey that is a
string. We'll look at more code below.
Creating a TypeScript React Project#
So for getting started, thankfully,
create-react-app has a
typescript template that has everything setup for us!
You can create a new project like this:
It will create a bunch of files and the main addition is the
tsconfig.js file which configures the TypeScript compiler.
You'll also notice that our files end in
tsx instead of
Also you look at the
package.json you'll see some extra typescript packages, too - but because they work out of the box, we'll ignore those for now.
The main thing we're going to be doing with TypeScript and React is defining components. So it makes sense to look at the typing of those components first.
As you may know, there are two ways you can define components:
Class components or
Let's look at both:
Typing Class Components with TypeScript#
To keep it simple, let's take the classic case of a component that shows a counter with a message.
A bare-minimum TypeScript class component looks like this:
So far, so good, but we're missing two key parts of React components:
In React, we use props to pass down values into a component and state to manage state within a component.
By the way, if you're relatively new to React and this feels too advanced, you should checkout our free series 30 Days of React
If you think about it closely, props and state are both objects that need to have a type. Often we might have a component that has half-a-dozen props that could be passed in and we want to make sure that these props are valid keys AND valid values.
One of the great things about using TypeScript with React is that the compiler will enforce this for us.
So let's define an interface for the props to this component. We want to accept a
message into this component as props like this:
If you think about it, what we're doing is passing this object into the component:
We can define an interface in TypeScript that describes this object. We'll do it like this:
Adding Props Type#
But how do we tell React that the
CounterProps interface is the types for our component props?
We do this by using TypeScript generics on the
You can think of generics as parameters to a type. Generics let you make "configurable" types. It's easier if we look at the code:
Now let's say we try to call this component without passing a message prop like this:
... we get an error! Here's a screenshot from my VSCode:
There's a lot there, but the key point is that it's saying
Property 'message is missing'
Okay, so we know we need to pass a message prop. Let's try passing an invalid message prop and see what happens:
Above, the number
123 is invalid because we defined our
message to be a
string (not a
Here's what I get in VSCode:
Again, a lot there but the key idea is that it tells me:
Type 'number' is not assignable to type 'string'
Okay so that deals with typing props, what about state? We deal with that much the same way, first we'll define an interface for the state:
Let's keep our counter in state as the variable
Now we pass the
CounterState as the second parameter to the
Looking at the above code, you might be wondering, how would I ever know that state was the second generic parameter?
It's in the documentation (for the types). Just like how you learn the order of function parameters in, say
setTimeout, you look at the docs to learn the order of parameters for generics.
Typing class components is interesting, but they're also falling out of fashion. So how would we type functional components?
Well, the first way is that you don't have to add any types at all.
Because we are returning JSX, TypeScript can automatically conclude that the return type of our function is
JSX.Element, which is good enough for React.
Of course, if you want to be verbose, you can use
But what about props?
Well, with a functional component, props are just arguments so we can just type the arguments:
Above we type the
CounterProps. However, a more idomatic React style would be to destructure the props into variables like this:
What about functional state?
When we have a functional component we use hooks to manage state. Specifically we're going to use the
Here's what our component looks like with keeping a basic counter with
So in this case, if I was keeping simple values we don't need to add any extra types at all. If we keep one value within
useState, it's pretty easy.
Buuuuut if you've worked with React long enough you're probably suspicious of my solution here. What about in the more complicated case where our state is an object, like in our class above?
Now, I'd like to point out, that in such a simple case I probably wouldn't store an object in
useStateas a matter of style. But I want to show you a more complicated case as it represents something that might come up in your work.
Say we want our state to be the same
To type this, we use the generics on
Above, notice the line with
useState<CounterState> - there, we're telling TypeScript that the object in
useState is of type
CounterState - and so it needs to enforce that "shape" on any value it controls.
So there you have it, that's the absolute basics of using React with TypeScript. You can get pretty far on just the above tips.
But there's still a lot more to learn like:
How do we type more complex hooks like
How do we add types for a custom React library?
What if we're using GraphQL and we want typed queries?
We cover the answers to these questions and more in Fullstack React with TypeScript.