How to create React components with TypeScript

Using TypeScript together with React has proven to be a powerful combination.

Some people are afraid to move to TypeScript because they think it forces you to write a lot of boilerplate code. In my experience, once these people give TypeScript a try, they won't go back to writing Vanilla JavaScript again.

I want to take this fear from you by showing how you can easily define your React components with TypeScript in this article.

Functional components with TypeScript

You can create functional components in TypeScript just like you would in JavaScript.

The main difference is the FC interface, which stands for Function Component. We use this to tell TypeScript that this is a React function component and not just a regular function.

Optionally, we can add types for the props by defining an interface and passing it to the generic FC.

A functional component then typically looks like this:

import React, { FC } from 'react';

interface TitleProps {
  title: string;
}

const Title: FC<TitleProps> = ({ title, subtitle }) => {
  return (
    <>
      <h1>{title}</h1>
      <h2>{subtitle}</h2>
    </>
  );
};

export default Title;

If we now use this component somewhere in our application, we get to reap the benefits of this:

We get autocompletion and compilation errors in our editor that warn us when we forgot a property or use the wrong types.

Compilation error example

Generic types in TypeScript are like functions that accept parameters. Instead of parentheses, generic types use angle brackets (<>).

For the FC interface, you don't need to pass any parameters, so you could use it without a generic type by omitting the angle brackets altogether when your component doesn't accept any props:

const Title: FC = () => {
  // ...
}

Optional props

Not all of your props need to be required. We can make a prop optional by adding a question mark to the interface:

interface TitleProps {
  title: string; // required
  subtitle?: string; // optional
}

This will allow you to omit the prop when you don't need it.

Alternatives to the FC interface

If you already know TypeScript, you might have wondered why I'm not applying the prop interface to the function parameters itself.

const Title = ({ title, subtitle }: TitleProps) => {
  // ...
}

The syntax above works fine as well. In fact, many people write function components like this in TypeScript.

However, the reason why you might want to use a generic type like FC is that this comes with all the typings that you could possibly need for a function component.

This includes, for example, the implicit children property. Being a default prop of any React component, we don't need to add it to our interface.

import React, { FC } from 'react';

interface TitleProps {
  title: string;
  subtitle?: string;
}

const Title: FC<TitleProps> = ({ title, subtitle, children }) => {  return (
    <>
      <h1>{title}</h1>
      <h2>{subtitle}</h2>
      <div>{children}</div>
    </>
  );
};

export default Title;

This can be seen as a disadvantage though. Imagine you don't want your component to accept children. The FC interface always adds the children prop, regardless of whether you use it or not.

It would be better to define the props without React's generic interface and use FC in the case you want to access the children property.

FC vs FunctionComponent

I've come across the FC and the FunctionComponent interfaces and wondered what advantages one had over the other.

A quick look at React's type definitions revealed that FC is just the shorthand for FunctionComponent. Both refer to the same interface.

type FC<P = {}> = FunctionComponent<P>;

Type-safe state in function components

To have type-safety for the state in functional components, we don't necessarily need to modify our code: The useState hook inherits the type from the value we use to initialize it.

If we use a more complex type or don't initialize the state, we can pass the type like the following:

const [title, setTitle] = useState<string>(null);

Replace string with whatever type you need.

Class components in TypeScript

Although I default to writing functional components, some cases require me to work with class components as well.

Let's jump right into it and have a look at the previous example as a class component in TypeScript.

import React, { Component } from 'react';

interface TitleProps {
  title: string;
  subtitle?: string;
}

class Title extends Component<TitleProps> {
  render() {
    const { title, subtitle, children } = this.props;
    return (
      <>
        <h1>{title}</h1>
        <h2>{subtitle}</h2>
        <div>{children}</div>
      </>
    );
  }
}

export default Title;

As we can see here, we extend React's Component class just like we would when working with JavaScript.

However, this class interface is generic, so we can pass an interface to it that contains the types of our props Component<TitleProps>.

Type-safe state in class components

We can also make our state type-safe by creating an interface for it and passing it as a parameter to Component:

import React, { Component } from 'react';

interface TitleProps {
  title: string;
  subtitle?: string;
}

interface TitleState {  counter: number;}
class Title extends Component<TitleProps, TitleState> {  // ...
}

export default Title;

Constructors

We'll typically use constructors to initialize our class component's state.

Unfortunately, the React class won't know the type of our props automatically, which is why we need to specify the type of the props parameter in the function call:

constructor(props: TitleProps) {  super(props);
  this.state = {
    counter: 0,
  };
}

If we don't do this and we run our code in strict mode, TypeScript will throw a compilation error.

Default props

We can define the default values of our properties by adding the static variable defaultProps to our class component:

interface TitleProps {
  title: string;
  subtitle?: string;
}

class Title extends Component<TitleProps, TitleState> {
  public static defaultProps = {    title: 'Hey there!',  };
  //...
}

The React component is smart enough to inherit the prop types.

In the example, title is a required property, however, since we've defined a default prop, it automatically becomes optional.

TypeScript won't throw an error if we don't add a title prop in this case, even though it's required in our interface.

Conclusion

As you can see, using TypeScript for your React isn't too complicated and the amount of code you need to write is limited.

In exchange for this bit of extra effort, you get rewarded with type-safety and autocompletion, which results in a more solid code-base as it helps you to catch bugs early on during development.

If you want to learn more about how to use TypeScript with React, make sure to subscribe to my newsletter, so you don't miss my future articles.

Previous

Getting started with React and TypeScript

2021-01-17 | 3 min
Learn how to add TypeScript to your React projects.

Next

React and TypeScript: How to find the right event type

2021-02-15 | 2 min
When getting started with TypeScript, finding the right interfaces can be challenging. In this article, you'll learn how to find the correct TypeScript interface for React events in any situation.

Support

PatreonKoFi