This site runs best with JavaScript enabled.

How to write a React Component in TypeScript

Photo by Jonny Gios


There are plenty of ways to do it, here's how I recommend typing React Components

Here's our component without types:

1const operations = {
2 '+': (left, right) => left + right,
3 '-': (left, right) => left - right,
4 '*': (left, right) => left * right,
5 '/': (left, right) => left / right,
6}
7
8function Calculator({left, operator, right}) {
9 const result = operations[operator](left, right)
10 return (
11 <div>
12 <code>
13 {left} {operator} {right} = <output>{result}</output>
14 </code>
15 </div>
16 )
17}
18
19const examples = (
20 <>
21 <Calculator left={1} operator="+" right={2} />
22 <Calculator left={1} operator="-" right={2} />
23 <Calculator left={1} operator="*" right={2} />
24 <Calculator left={1} operator="/" right={2} />
25 </>
26)

Right there you may notice we do things a little differently. Maybe you prefer this instead:

1const Calculator = ({left, operator, right}) => (
2 <div>
3 <code>
4 {left} {operator} {right} ={' '}
5 <output>{operations[operator](left, right)}</output>
6 </code>
7 </div>
8)

I don't like the implicit return there. It means you can't reasonably declare variables or use hooks. So even for simple components, I never go with this approach.

Ok, so maybe you do this:

1const Calculator = ({left, operator, right}) => {
2 const result = operations[operator](left, right)
3 return (
4 <div>
5 <code>
6 {left} {operator} {right} = <output>{result}</output>
7 </code>
8 </div>
9 )
10}

Honestly, that's fine most of the time. I personally like the hoisting characteristics of function declarations rather than function expressions like that (learn more).

Alright, let's add some types to this. For functions, you need to consider the types coming in and the types going out. Let's start with the input: props. To start, let's go with a simple type for the props (we'll improve it later):

1type CalculatorProps = {
2 left: number
3 operator: string
4 right: number
5}

With that, let's try some options for applying that type to the props object in our React Component.

A common method to typing a React component is to use one of the generics that are built-into @types/react (I mean, it's built-in right? So what could go wrong?). Interestingly, you cannot type a function declaration this way, so we'll have to use a function expression:

1const Calculator: React.FC<CalculatorProps> = ({left, right, operator}) => {
2 // implementation clipped for brevity
3}

This works pretty well, but there are three major problems with this:

  1. Our Calculator function now accepts a children prop, even though we don't do anything with it 🙃 (So, this compiles: <Calculator left={1} operator="+" right={2}>What?</Calculator>).
  2. You can't use generics. Not super common, but definitely a downside.
  3. We have to use a function expression and can't use a function declaration.

Ok ok, so maybe #3 isn't a major problem, but #1 is pretty significant. There are a few other smaller issues laid out in this excellent GitHub issue if you want to dive deeper (also check the React TypeScript Cheatsheet). Suffice it to say, don't use React.FC (or its longer alias React.FunctionComponent).

One of the things I love about React components is that they aren't all that special. Here's the definition of a React component:

A React component is a function that returns something React can render.

Now, according to @types/react, we're limited to null and JSX.Elements, but React can actually render strings, numbers, and booleans as well. In any case, because a React component is simply a function that returns something React can render, typing it can be just as straightforward as typing functions. You don't have to do anything special just because it's React.

So here's how I'd type the props for this component:

1function Calculator({left, operator, right}: CalculatorProps) {
2 // implementation clipped for brevity
3}

This doesn't have any of the shortcomings of React.FC and it's no more complicated than typing the arguments to a regular function.

Ok, so what about the return value? Well, we could type it as React.ReactElement or even wider as a JSX.Element. But honestly, I side with my friend Nick McCurdy when he says that mistakes can easily be made causing the return type to be too wide. So even outside a react context, I default to not specifying the return type (rely on inference) unless necessary. And that's the case here.

Improving the CalculatorProps type

Ok, now this next bit doesn't have a lot to do with typing React components, but I thought you'd find it interesting anyway, so skip ahead if you don't. Let's improve the CalculatorProps type. As a reminder, here's what we have so far:

1// I took the liberty of typing each of these functions as well:
2const operations = {
3 '+': (left: number, right: number): number => left + right,
4 '-': (left: number, right: number): number => left - right,
5 '*': (left: number, right: number): number => left * right,
6 '/': (left: number, right: number): number => left / right,
7}
8
9type CalculatorProps = {
10 left: number
11 operator: string
12 right: number
13}
14function Calculator({left, operator, right}: CalculatorProps) {
15 const result = operations[operator](left, right)
16 return (
17 <div>
18 <code>
19 {left} {operator} {right} = <output>{result}</output>
20 </code>
21 </div>
22 )
23}

I think the left and right types are fine. It's the operator that I'm unhappy with. Using string is too wide. There are specific operations that are allowed. For example, what would happen if we tried:

1const element = <Calculator left={1} operator="wut" right={2} />

That right there is what we call a runtime exception my friends. That is... unless you have strict mode on, in which case you'd have a compilation error on operations[operator]. In strict mode, TypeScript will correctly know that accessing any string from the operations object will not necessarily return a callable function.

There are plenty of ways to solve this problem. Basically, we want to limit the operator to only the supported operators. We can do that with a simple union type:

1type CalculatorProps = {
2 left: number
3 operator: '+' | '-' | '*' | '/'
4 right: number
5}

But if we decided to add the Exponentiation Operator (**), then we'd have to update not only the operations object, but also the operator type which would be annoying. Maybe there's a way we can derive the type for the operator based on the operations object? Why, yes there is!

1type CalculatorProps = {
2 left: number
3 operator: keyof typeof operations
4 right: number
5}

typeof operations is going to get us a type that describes the operations object, which is roughly equal to:

1type operations = {
2 '+': (left: number, right: number) => number
3 '-': (left: number, right: number) => number
4 '*': (left: number, right: number) => number
5 '/': (left: number, right: number) => number
6}

The keyof part will take all the keys of that type, resulting in '+' | '-' | '*' | '/' 🎉

Conclusion

Here's the finished version (I typed the operations functions as well):

1const operations = {
2 '+': (left: number, right: number): number => left + right,
3 '-': (left: number, right: number): number => left - right,
4 '*': (left: number, right: number): number => left * right,
5 '/': (left: number, right: number): number => left / right,
6}
7
8type CalculatorProps = {
9 left: number
10 operator: keyof typeof operations
11 right: number
12}
13
14function Calculator({left, operator, right}: CalculatorProps) {
15 const result = operations[operator](left, right)
16 return (
17 <div>
18 <code>
19 {left} {operator} {right} = <output>{result}</output>
20 </code>
21 </div>
22 )
23}
24
25const examples = (
26 <>
27 <Calculator left={1} operator="+" right={2} />
28 <Calculator left={1} operator="-" right={2} />
29 <Calculator left={1} operator="*" right={2} />
30 <Calculator left={1} operator="/" right={2} />
31 </>
32)

I hope that gives you an idea of a good way to type your React components. Good luck and take care!

P.S. One thing I don't like at all about our solution is we have to type each of the operations functions. Interestingly, this is a bit of a rabbit hole, but at the other end of it, the types are definitely better and you learn a few tricks along the way. Originally that was part of this blog post, but I decided to move it to its own post. Read all about it here: How to write a Constrained Identity Function (CIF) in TypeScript.

Discuss on TwitterEdit post on GitHub

Share article
EpicReact.Dev

Get Really Good at React

Blast Off

Write professional React.

TestingJavaScript.com

Your Essential Guide to Flawless Testing

Start Now

Write well tested JavaScript.

Kent C. Dodds

Kent C. Dodds is a JavaScript software engineer and teacher. He's taught hundreds of thousands of people how to make the world a better place with quality software development tools and practices. He lives with his wife and four kids in Utah.

Join the Newsletter



Kent C. Dodds