A Quick Guide to React with TypeScript

How to combine two of the most popular tools in web development

Published on
Apr 16, 2020

Read time
5 min read

Introduction

Right now, Typescript and React and two of the most popular technologies in web development. In this article, we’ll look into using the two technologies in tandem, covering basic type checking in functional and class components. The article’s intended for anyone who’s bringing TypeScript and React together for the first time, but it should also be useful for those who need a quick reminder of the syntax.

What is React?

React (maintained by Facebook) is a declarative, component-based library. It makes it easy to build interactive, reusable components which — thanks to its virtual DOM — update in a highly efficient way.

What is Typescript?

Typescript (built by Microsoft) is a superset of JavaScript: it contains all the features of JavaScript, plus a variety of type-checking features reminiscent of strongly-typed languages like Java and C#. It makes code easier to read, understand and debug.

Making a React-TypeScript Project

The easiest way to get started with both technologies is to use the create-react-app package, plus --template typescript. Choose npx or yarn and run:

## npx
npx create-react-app app-name --template typescript## yarn
yarn create react-app app-name --template typescript

Building a Simple Counter

To cover the basic use-cases of Typescript in React, we’ll make a basic Counter component. First, we’ll use a class component. Then, we’ll build an identical feature with a functional component and hooks.

To follow along, create a components folder inside src, and add ClassCounter.tsx and FunctionalCounter.tsx to this folder:

cd src && mkdir components && cd components && touch ClassCounter.tsx && touch FunctionalCounter.tsx

In the next section, we’ll look at the class and functional variants in turn, and discuss the key differences.

Our counter will have two buttons, one with a plus and one with a minus, and we’ll decide whether to add or subtract based on the innerText of whichever button is clicked. (Of course, we could just use separate increment and decrement functions, but I wanted to show you how to type-check events!)

The Class Component Version

import React, { PureComponent } from "react";

class ClassCounter extends PureComponent<{}, { count: number }> {
  state = { count: 0 };

  updateCount = (e: React.MouseEvent<HTMLButtonElement>): void => {
    switch (e.currentTarget.innerText) {
      case "-":
        this.setState({ count: this.state.count - 1 });
        break;
      case "+":
      default:
        this.setState({ count: this.state.count + 1 });
        break;
    }
  };

  render(): JSX.Element {
    const { count } = this.state;
    return (
      <div>
        <h2>Class Component</h2>
        <h1>{count}</h1>
        <button onClick={this.updateCount}>+</button>
        <button onClick={this.updateCount}>-</button>
      </div>
    );
  }
}

export default ClassCounter;

Because our counter involves changing a simple type (the only value held in the state is a number), we can use a PureComponent rather than a Component, as that only performs a shallow comparison.

We type check the props and state immediately after PureComponent. Because there aren’t any props, this is represented as an empty object {}, while the state is { count: number }.

The next example of type checking occurs in our method, updateCount. We need to access the event object passed by the mouse click. Because React overrides the native MouseEvent, we use React.MouseEvent and then specify the element type in angle brackets: in this case, HTMLButtonElement. Because this function doesn’t return anything, we set a return type of void.

Finally, the render method returns a JSX.Element.

The Functional Component Version

import React, { FC, useState, useCallback } from "react";

const FunctionalCounter: FC<{}> = (): JSX.Element => {
  const [count, setCount] = useState(0);

  const updateCount = useCallback(
    (e: React.MouseEvent<HTMLButtonElement>): void => {
      switch (e.currentTarget.innerText) {
        case "-":
          setCount(count - 1);
          break;
        case "+":
        default:
          setCount(count + 1);
          break;
      }
    },
    [count]
  );

  return (
    <div>
      <h2>Functional Component</h2>
      <h1>{count}</h1>
      <button onClick={updateCount}>+</button>
      <button onClick={updateCount}>-</button>
    </div>
  );
};

export default FunctionalCounter;

When using functional components with TypeScript, you’ll need to import the FC interface from React. This is followed by the props type in angle brackets, but as we have no props, we’ll use an empty object {}. The return type is JSX.Element.

One of the main differences in the functional component version is that we don’t need to type state. That’s because the type is assumed from whatever argument is provided in useState — in this case 0, a number.

Our updateCount function is virtually identical to the updateCount method in our class component, except this time it’s wrapped inside useCallback. This provides a similar benefit to using a PureComponent. This will “memoize” the callback, meaning that it will only change if one of the dependencies has changed, preventing unnecessary re-renders. These dependencies are specified in the second argument of useCallback.

Adding Props

So far, we've covered the main type checking syntax you’ll encounter when using TypeScript with React. But there’s a part of our design which could be improved. At the moment, we’re relying on the innerText of our buttons to determine whether to increment or decrement our count. But the innerText could be anything. What if another developer wanted to change the innerText to the word “plus” — or use a symbol? Then our function would break!

Typescript can help prevent this. To do this, let’s create a new Button component which accepts either "+" or "-".

The Class Component Version

First, we’ll define a new type type ButtonType = "+" | "-". We’ll then create a Button component which takes a type prop and updateCount prop, both of which are typed in the angle brackets.

type ButtonType = "+" | "-";

class Button extends PureComponent<
  { type: ButtonType; updateCount: (type: ButtonType) => void },
  {}
> {
  render(): JSX.Element {
    const { type, updateCount } = this.props;
    return <button onClick={() => updateCount(type)}>{type}</button>;
  }
}

Then, in our class counter component, we can replace the old button elements with this:

<Button updateCount={this.updateCount} type="+" />
<Button updateCount={this.updateCount} type="-" />

Now, if a developer tries to pass anything other than "+" or "-" to one of the buttons, TypeScript will throw an error.

The (Stateless) Functional Component Version

To me, the functional variant of the Button component demonstrates that stateless functional components are often easier to read — in simple cases like this — than classes:

type ButtonType = "+" | "-";
const Button = ({
  type,
  updateCount,
}: {
  type: ButtonType;
  updateCount: (type: ButtonType) => void;
}): JSX.Element => <button onClick={() => updateCount(type)}>{type}</button>;

As there’s now state, we don’t need to specify FC like we do in the FunctionalCounter component. In this component, we can now replace the button elements with this:

<Button updateCount={updateCount} type="+" />
<Button updateCount={updateCount} type="-" />

If you’re just starting to use TypeScript in React, or you wanted a quick refresher, then I hope this article’s provided a useful summary of the most common syntax — as well as revealing some of the differences between class components and functional components along the way. If you’ve got any feedback or questions, make sure to leave a comment!

© 2024 Bret Cameron