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!
Related articles
You might also enjoy...
Bad Abstractions Could Be Ruining Your Code
Why the ‘Don’t Repeat Yourself’ principle might be doing more harm than good
5 min read
How to learn to code and land your first job in 2024
How I would get my first job as a self-taught developer if I started over
7 min read
Building an International Website With Next.js
3 solutions to toggling routes on and off for different regions
7 min read