TypeScript is awesome. It allows one to remain in the fun wild-west of JavaScript, while being safe in the blanket of a state of the art type system. Plus, it a great way to adopt strong type checking gradually without having to rewrite the entire codebase, or get a Ph.D in type theory (I'm looking at you, Haskell 🦥).
But TypeScript is also has a learning curve. For some of us, it's a steep one. Everything is well documented, but there is a lot of the said documentation 😅. There are also cheetsheets, say for using TypeScript with React, but they assume a basic understanding of TypeScript. So what is missing (or at least I haven't found) are quick recipes for the most common use cases we run into when trying to introduce TypeScript into an existing React app. Here goes.
The minimum TypeScript you need to know to type your way around React
Let us start with a simple React component:
// Hello.tsx
import React from 'react';
const Hello = () => {
return <div>Hello</div>;
};
To add types to this component, we need to add a type annotation to Hello
.
This is what React calls as a "Function Component", or "FC" for short. React is
a bro, and also provides us with its (TypeScript) type — React.FC
:
import React from 'react';
const Hello: React.FC = () => { return <div>Hello</div>;
};
This (and the other types we'll add below) are part of the @types/react
and
@types/react-dom
packages, so you'll need to have them installed.
Moving on. A hello is nice, but saying hello while addressing a person by their
name is nicer. Let's teach our Hello
component to accept a name
prop:
import React from 'react';
const Hello: React.FC = ({ name }) => { return <div>{`Hello, ${name}!`}</div>;
};
Oh noes, the dreaded red squiggly appears.
This is a good time to bring up an important point. TypeScript does its thing at compile-time (when we write the code). So even though it is complaining here, when we actually run the code ("runtime"), it will work as normal. In that sense, this is more of a warning than an error.
So if we ignore TypeScript errors, things might or might not work at runtime. But if we don't ignore them, we can be sure that things will work at runtime (the things that TypeScript can check for, that is).
Back to the squiggly. What tsc
(TypeScript Compiler) is telling us
is that a plain React.FC
does not take any props, but here we're trying to
read a name
.
So we need to tell tsc that this is not a plain React.FC
, it is a enhanced
version that also takes a name
prop, and that this name
is a string.
import React from 'react';
const Hello: React.FC<{ name: string }> = ({ name }) => { return <div>{`Hello, ${name}!`}</div>;
};
This satisfies tsc. But it is not very readable, and it'll keep getting gookier
the more props we add. So let us define a type that describes the props that our
component takes, and then use that type to enhance React.FC
.
import React from 'react';
interface HelloProps { name: string;}
const Hello: React.FC<HelloProps> = ({ name }) => { return <div>{`Hello, ${name}!`}</div>;
};
Perfect.
Or not quite yet. What good is a web developer who greets a user without a CTA in mind? 😛
But our Hello component is perfect as it is – like a textbook React component, it does one thing, and does one thing well, and is a great building block. Adding new functionality to this would spoil that perfection.
So let us pass the CTA as a child. This way, we can reuse the Hello
component
in different places to greet the user the same way yet call on them to do
different things.
import React from 'react';
interface HelloProps {
name: string;
}
const Hello: React.FC<HelloProps> = ({ name, children }) => { return (
<div>
<div>{`Hello, ${name}!`}</div>
{children} </div>
);
};
Oh noes, the red squiggly reappears! This time it is complaining that
Property 'children' does not exist on type 'HelloProps'.
Fair enough. To fix this, we can add children
to HelloProps
, though for
doing that we'll need to figure out what is the type of children
.
But wait a sec. Taking children props looks like a common enough, bread and butter need for React components. Isn't there a standard type for such components?
Why, indeed there is. It is called PropsWithChildren
(yay for nice descriptive
names!).
import React, { PropsWithChildren } from 'react';
interface HelloProps {
name: string;
}
const Hello: React.FC<PropsWithChildren<HelloProps>> = ({ name, children }) => { return (
<div>
<div>{`Hello, ${name}!`}</div>
{children}
</div>
);
};
Brilliant.
We can't wait to use this to yell at greet our users, and immediately start
using it. It works great for a while, but then we run into a special page. Turns
out, we do indeed need to yell at the user on the pricing page, to make sure
they know that we really really like them.
Helpfully, our designer has written some nice CSS to style the yell appropriately, we just need to add that class to our div.
import React, { PropsWithChildren } from 'react';
interface HelloProps {
name: string;
}
const Hello: React.FC<PropsWithChildren<HelloProps>> = ({
name,
className, children,
}) => {
return (
<div>
<div className={`{className ?? ''}`}>{`Hello, ${name}!`}</div>
{children}
</div>
);
};
Guess what, the red squiggly strikes again!
Property 'className' does not exist on type 'PropsWithChildren<HelloProps>'
We now know what this means, and how to fix it – we need to add className
to
our props. But hold on, this too seems like a very standard, bread and butter
type(!) thing for React components. Isn't there a standard type to say that we'd
like className
, or style
, or id
or any one of the other such props that
are accepted by HTML div elements?
Glad you asked, because there is. It is called HTMLAttributes
.
But it doesn't jive on its own – we also need to tell it which HTML element it
is whose HTML attributes we're talking about. In our case, we're talking of a
standard HTML div, so we will use HTMLAttributes<HTMLDivElement>
.
import React, { HTMLAttributes, PropsWithChildren } from 'react';
interface HelloProps extends HTMLAttributes<HTMLDivElement> {
name: string;
}
const Hello: React.FC<PropsWithChildren<HelloProps>> = ({
name,
children,
...rest}) => {
return (
<div>
<div {...rest}>{`Hello, ${name}!`}</div> {children}
</div>
);
};
That's it, really. Armed with these basic types, you'll know how to type your way around React.
You'll hit more advanced cases eventually – say that instead of a vanilla div,
you wish to create something that behaves like the HTML link (<a>
- the anchor
tag). But you won't remain befuddled for long on that, for now you know that you
just need to find what's the HTMLDivElement
equivalent for a anchor tag and
use that instead. And these types are quite nicely named, so that type is
usually something whose name you can guess (for example, for an <a>
tag, it is
HTMLAttributes<HTMLAnchorElement>
).
Hope this was helpful! If you'd like to know more of our adventures with TypeScript, React, and other things of the ilk, do follow us on Twitter, or come hang out on our lovely Discord.
Tell next time, happy typ(e)ing! 🤓