Introduction

TypeScript is awesome. So is React. Let’s use them both together! Using TypeScript allows us to get the benefits of IntelliSense, as well as the ability to further reason about our code. As well as this, adopting TypeScript is low-friction, as files can be incrementally upgraded without causing issues throughout the rest of your project.

Prerequisites

If you would like to follow along with this guide, you will need:

One Ubuntu 20.04 server set up with a non-root user with sudo privileges and firewall enabled. You can do this by following the Ubuntu 20.04 initial server setup guide.
Node.js and npm installed. You can do this with Apt using a NodeSource PPA. Follow Option 2 of our guide on How To Install Node.js on Ubuntu 20.04 to get started.

Setting Up Your Project:

Assuming you’ve followed the prerequisite guide and installed Node.js and npm, begin by creating a new directory. Here we’ll call it react-typescript:

mkdir react-typescript

Change to this directory within the terminal:

cd react-typescript/

Then initialize a new npm project with defaults:

npm init -y

Wrote to /home/sammy/react-typescript/package.json:

{
  "name": "react-typescript",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

After this is initialized, next, you’ll install the appropriate React dependencies.

Installing React Dependencies

First, install the React dependencies react and react-dom:

sudo npm install react react-dom

Next, create a folder called src:

mkdir src

Change into that src directory:

cd src/

Then create the index.html file to insert into the src folder. You can do this with your preferred text editor. Here we’ll use nano:

nano index.html

Now that the index.html file is open, you can make some edits and add the following contents:
index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <title>React + TypeScript</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
  <div id="main"></div>
  <script type="module" src="./App.tsx"></script>
</body>
</html>

When you’re done, save and close the file. If you’re using nano, you can do this by pressing CTRL + X, then Y and ENTER.
We’ll be using Parcel as our bundler, but you can elect to use webpack or another bundler if you wish. Or, check this section if you prefer using Create React App. Let’s first install Parcel to add to our project:

sudo npm install parcel

Next, install Typescript:

sudo npm install --location=global typescript

Then install types for React and ReactDOM:

sudo npm install @types/react @types/react-dom

After everything has been installed, return to the parent directory by using two periods:

cd ..

Starting and Running the Development Server

Next, update package.json with a new task that will start your development server. You can do this by first creating and opening the file with your preferred text editor:

nano package.json

Once you’ve opened the file, replace the existing content with the following:
package.json

{
  "name": "react-typescript",
  "version": "1.0.0",
  "description": "An example of how to use React and TypeScript with Parcel",
  "scripts": {
    "dev": "parcel src/index.html"
  },
  "keywords": [],
  "author": "Paul Halliday",
  "license": "MIT"
}

When you’re done, save and close the file.
Now, change back into the src directory:

cd src/

Once you’re in the directory, create and open a Counter.tsx file that includes a basic counter:

nano Counter.tsx

Add the following contents to the file:
Counter.tsx

import * as React from 'react';

export default class Counter extends React.Component {
  state = {
    count: 0
  };

  increment = () => {
    this.setState({
      count: (this.state.count + 1)
    });
  };

  decrement = () => {
    this.setState({
      count: (this.state.count - 1)
    });
  };

  render () {
    return (
      <div>
        <h1>{this.state.count}</h1>
        <button onClick={this.increment}>Increment</button>
        <button onClick={this.decrement}>Decrement</button>
      </div>
    );
  }
}

When you’re done save and close the file.
Then create the App.tsx file with your preferred text editor:

nano App.tsx

Now that the file is open, add the following information to load the Counter:
App.tsx

import * as React from 'react';
import { render } from 'react-dom';

import Counter from './Counter';

render(<Counter />, document.getElementById('main'));

Once you’ve added the content, save and close the file.
Then update your firewall permissions to allow for port traffic for 1234:

sudo ufw allow 1234

Finally, run your project with the following command:

sudo npm run dev

> react-typescript@1.0.0 dev
> parcel src/index.html

Server running at http://localhost:1234
✨ Built in 3.84s

Confirm everything is working by navigating to http://your_domain_or_IP_server:1234 in your browser. You should have the following increment and decrement functions on your page:
webpage with increment and decrement functions
Your sample project is now successfully up and running.

Create React App and TypeScript

If you’d rather use Create React App to initiate your project, you’ll be pleased to know that CRA now supports TypeScript out of the box.
Use the --typescript flag when invoking the create-react-app command:

Functional Components

Stateless or functional components can be defined in TypeScript as:

import * as React from 'react';

const Count: React.FunctionComponent<{
  count: number;
}> = (props) => {
  return <h1>{props.count}</h1>;
};

export default Count;

We’re using React.FunctionComponent and defining the object structure of our expected props. In this scenario, we’re expecting to be passed in a single prop named count and we’re defining it in-line. We can also define this in other ways, by creating an interface such as Props:

interface Props {
  count: number;
}

const Count: React.FunctionComponent<Props> = (props) => {
  return <h1>{props.count}</h1>;
};

Class Components

Class components can similarly be defined in TypeScript as in the following:

import * as React from 'react';

import Count from './Count';

interface Props {}

interface State {
  count: number;
};

export default class Counter extends React.Component<Props, State> {
  state: State = {
    count: 0
  };

  increment = () => {
    this.setState({
      count: (this.state.count + 1)
    });
  };

  decrement = () => {
    this.setState({
      count: (this.state.count - 1)
    });
  };

  render () {
    return (
      <div>
        <Count count={this.state.count} />
        <button onClick={this.increment}>Increment</button>
        <button onClick={this.decrement}>Decrement</button>
      </div>
    );
  }
}

Default Props

We can also define defaultProps in scenarios where we may want to set default props. We can update our Count example to reflect the following:

import * as React from 'react';

interface Props {
  count?: number;
}

export default class Count extends React.Component<Props> {
  static defaultProps: Props = {
    count: 10
  };

  render () {
    return <h1>{this.props.count}</h1>;
  }
}

We’ll need to stop passing this.state.count in to the Counter component too, as this will overwrite our default prop:

render () {
  return (
    <div>
      <Count />
      <button onClick={this.increment}>Increment</button>
      <button onClick={this.decrement}>Decrement</button>
    </div>
  )
}

You should now have a project that’s set up to use TypeScript and React, as well as the tools to create your own functional and class-based components!