4 Comments

React and TypeScript – The Basics

React is great, and with TypeScript, it can be even better.

If you haven’t used TypeScript with React, you might be wondering how much work is required to get started, and how React development with TypeScript is different than JavaScript. I’m going to address these questions, covering everything I would have liked to find in one place when I was getting started with TypeScript—specifically, what is required to set up a React/TypeScript project, and how some of the basic React/Redux type definitions work.

Project Setup

Whether you are starting from scratch or interested in migrating to TypeScript, the following will help you get your project configured correctly.

webpack

Most React projects use webpack to manage their build process (transcompilation, module loading, etc.). Webpack defers to other libraries (e.g. Babel) for transpiling source JavaScript to a flavor that can run in most (or hopefully all) browsers. For example, many projects develop in ES6 or above, and then transpile down to ES5 (this page details which browsers currently support ES5).

A webpack config file that uses Babel for this type of transcompilation might look like this:


module.exports = {
  entry: 'index.jsx',
  output: {
    filename: 'bundle.js'
  },
  module: {
    loaders: [
      { test: /\.jsx$/, loader: 'babel-loader', exclude: /node_modules/ }
    ]
  }
}

TypeScript works largely the same way. Instead of babel-loader, you will use a TypeScript loader. There are currently two decent options:

  1. awesome-typescript-loader
  2. ts-loader

I have only used awesome-typescript-loader. Both can be installed via npm or Yarn. You will also need to install TypeScript.

yarn add awesome-typescript-loader typescript

The TypeScript webpack config looks like this:


module.exports = {
  entry: 'index.tsx',
  output: {
    filename: 'bundle.js'
  },
  module: {
    loaders: [
      { test: /\.tsx$/, loader: 'awesome-typescript-loader' },
    ],
  }
}

You will also need to define a tsconfig.json file in your project root. This file is used to configure the TypeScript compiler. I believe the bare minimum tsconfig.json you need for React development is:


{
  "compilerOptions": {
    "jsx": "react",
  }
}

(The full list of compiler options is defined here). Unless you specify a value in your tsconfig.json, the compiler will use default values.

One interesting compiler flag to consider setting is:


"noImplicitAny": true

This requires a type be specified for everything–and forces you to mark something as an any type if the compiler can not automatically infer a more specific type.

I would strongly recommend this for new projects. However, if you are introducing TypeScript into an existing codebase, it may not be practical to use, as you will have to manually type-annotate your existing codebase.

Execution

Using a REPL or executing parts to your source code directly (without transpiling) is a requirement for most projects. This is useful for things like maintenance or deployment scripts, and sometimes for running tests. You can certainly webpack your tests, but you don’t necessarily need to (there are advantages for either approach).

ts-node enables you to execute TypeScript directly, just as you might execute JavaScript code using Node. It works like this:

ts-node foo.ts

If you happen to be writing a Node backend, you could even use ts-node to execute it without transpiling, though this isn’t recommended for production.

Installing npm packages

It’s important to briefly discuss installing packages in TypeScript. In general, you npm install or yarn add dependencies the same way you would in a JavaScript application. However, there may be one additional step to install type definitions.

Certain libraries define their own type definitions alongside their source code, in which case there is no extra step required. However, other libraries don’t define types with their source code. Some may not define types at all.

The Definitely Typed project manages type definitions for an incredibly large number of libraries. Often, library authors choose to add types for their library to Definitely Typed instead of alongside their library code.

When installing a library in TypeScript, it is useful to install the library normally, and also install types from Definitely Typed. Types can usually be installed via prefixing the library name with @types/. For example, if I wanted to install lodash, I would run:

yarn add lodash @types/lodash

This would install the lodash library and the type definitions for it.

Very rarely, libraries will not have type definitions (let me stress how rare this is; the TypeScript community is great about adding type definitions to Definitely Typed). In this case, you can either define your own types or choose to any type everything, which is not preferable, but will essentially tell TypeScript to get out of your way.

That does it for project setup. You should now be able to compile and run TypeScript code and install type definitions for third-party libraries. If you don’t want to get bogged down in project setup, I have put together a React-Redux-TypeScript starter kit. You can clone it and be ready to go immediately.

Writing TypeScript Code

Now that we have covered setting up your React project using TypeScript, let’s talk about how some of the basic type definitions work.

Components

React Components are defined in TypeScript like this:


type Props = { ... };
type State = { ... };

class MyComponent extends React.Component<Props, State> {
  ...
}

They look almost exactly the same as JavaScript, except for the two generic React.Component types: Props and State. These define the Props and State for the component. If your component code attempts to access Props or State values that don’t exist, or uses them in ways that aren’t supported based on their types, the TypeScript compiler will throw an error.

To further illustrate the power of this, let’s look at a more concrete example. Suppose we need to define a Vehicle component that has the props vehicleType, color, numWheels, and an age. In TypeScript, this could be defined as:


type Props = {
  vehicleType: "Car" | "Truck" | "Motorcycle",
  color: string,
  numWheels: 2 | 4,
  age: number,
};
type State = {};

class Vehicle extends React.Component<Props, State> {
  render() {
    <div>
      <div>VehicleType: {this.props.vehicleType}</div>
      <div>Color: {this.props.color}</div>
      <div>Wheels: {this.props.numWheels}</div>
      <div>Age: {this.props.age}</div>
    </div>
  }
}

The Props type is doing some interesting things. We have defined age to be a number, and color to be a string, but vehicleType is defined to be a string a value of “Car,” “Truck,” or “Motorcycle.” Similarly, numWheels is either the number 2 or 4. This is called a TypeScript union type.

The above component would be used like this:


<Vehicle vehicleType="Car" color="Red" numWheels={4} age={5} />
<Vehicle vehicleType="Motorcycle" color="Blue" numWheels={2} age={10} />

If we tried to instantiate a component with a number for a color, or a string for age, we would get a compiler error. What’s more interesting is that if we tried to assign the number 3 to numWheels, we would also get a compiler error. It would look like this:

Types of property 'numWheels' are incompatible. Type '3' is not assignable to type '2 | 4'.

That’s pretty nice. The TypeScript type system is super flexible and allows you to be very specific for each type definition in your code.

You may have noticed that our types still allow a car or truck to be defined with two wheels, and a motorcycle to be defined with four wheels. However, we could get a bit more clever with our types to prevent this. Suppose we updated our Props type to this:


type FourWheeledVehicle = { vehicleType: "Car" | "Truck", color: string, numWheels: 4, age: number };
type TwoWheeledVehicle = { vehicleType: "Motorcycle", color: string, numWheels: 2, age: number };
type Props = FourWheeledVehicle | TwoWheeledVehicle;

Now, we can’t assign two wheels to a “Car” or four wheels to a “Motorcycle.” I hope you see how this flexibility allows you to get very specific with your type definitions, and prevent all sorts of invalid scenarios at compile time.

Note: I wanted to mention that React does have a solution for this: PropTypes. Full disclosure: I haven’t used it much, but it does seem pretty flexible. The big disadvantage is that it will only allow you to type-check your component props (not other parts of your code). TypeScript will provide full end-to-end type coverage.

Redux

Most projects use Redux alongside React, so it’s worth taking a look at how TypeScript works with the Redux.

My colleague Drew Colthorp has written extensively about using TypeScript with Redux–specifically about how you can type your actions to improve your reducer code.

I won’t rehash this, but the above two posts are well worth a read. Instead, I want to focus on how the Redux connect function works in TypeScript.

The Redux connect function describes how to pull data out of your Redux store and make it available to your React components. It also wires up callbacks that your React components can call in response to application events (e.g., user input). It achieves this by requiring two functions: mapStateToProps and mapDispatchToProps. Usage of the connect function in TypeScript is the same as in JavaScript, though defining the correct types can be tricky.

Let’s look at an example. The types for the connect function are defined as follows:

const ConnectedComponent = connect<StateProps, DispatchProps, OwnProps>(
  mapStateToProps,
  mapDispatchToProps,
);

Here, there are three generic parameters that the connect function expects: StateProps, DispatchProps, and OwnProps.

StateProps describes the shape of the object that is returned by mapStateToProps. Similarly,
DispatchProps describes the shape of the object that is returned by mapDispatchToProps. OwnProps are the props that this component expects the caller to provide to it.

As long as you remember those three generic types, writing container components in TypeScript should be easy. It’s also extremely type-safe. For example, if code using your container component did not provide props in the shape of OwnProps, you would receive a compiler error.

Other Resources

I hope this post helps you get off the ground and enables you to start using TypeScript in your React apps. If you have any questions about using TypeScript with React, please leave me a comment or shoot me an email. Below are some other blog posts that you might find helpful.