DEV Community

ente
ente

Posted on • Originally published at ente.io

The minimum TypeScript you need for React

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>;
};
Enter fullscreen mode Exit fullscreen mode

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>;
};
Enter fullscreen mode Exit fullscreen mode

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>;
};
Enter fullscreen mode Exit fullscreen mode

Oh noes, the dreaded red squiggly appears.

Property 'name' does not exist on type '{}'

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 (Type*Script **C*ompiler) 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>;
};
Enter fullscreen mode Exit fullscreen mode

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>;
};
Enter fullscreen mode Exit fullscreen mode

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>
    );
};
Enter fullscreen mode Exit fullscreen mode

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>
    );
};
Enter fullscreen mode Exit fullscreen mode

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>
    );
};
Enter fullscreen mode Exit fullscreen mode

Guess what, the red squiggly strikes again!

Property 'className' does not exist on type 'PropsWithChildren'

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>
    );
};
Enter fullscreen mode Exit fullscreen mode

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! 🤓

Top comments (4)

Collapse
 
jackmellis profile image
Jack

I never cared for React.FC myself when you can just do

type Props { name: string, children: ReactNode }

const Hello = ({ name, children }: Props) => { ... }
Enter fullscreen mode Exit fullscreen mode

Ts automatically knows this is a component type and it's much less verbose than these examples.

Collapse
 
skube profile image
skube

But don't you get some extra type safety when using React.FC?

// @types/react
type FC<P = {}> = FunctionComponent<P>;

interface FunctionComponent<P = {}> {
  (props: P, context?: any): ReactElement<any, any> | null;
  propTypes?: WeakValidationMap<P> | undefined;
  contextTypes?: ValidationMap<any> | undefined;
  defaultProps?: Partial<P> | undefined;
  displayName?: string | undefined;
}
Enter fullscreen mode Exit fullscreen mode

Though I'm not exactly sure what those extra types do 😄

Collapse
 
jackmellis profile image
Jack

Yes you do but I guess after 5 years of React + Typescript I've never actually needed them 😅

Collapse
 
fyodorio profile image
Fyodor

That’s why some people hate TS 😅

FWIW in Angular TS integrates much more organically into the dev flow as compared to React or even Vue (where it’s rather afterthought, and that’s not an offense — there are other pretty nice approaches to using types with JS there actually).