Article summary
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:
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.
Thanks for a great article! One note: in the `ConnectedComponent` example, it may be convenient to merge the constituent props into the component props (e.g. https://rjzaworski.com/2016/08/typescript-redux-and-react#connection) and pass in both:
type Props = StateProps & DispatchProps & OwnProps
class Vehicle extends React.Component {
// …
}
const ConnectedVehicle = connect(
mapStateToProps,
mapDispatchToProps,
)(Vehicle);
Awesome work. Thanks for this
awesome :) great work this helps to start to code in TS
Doesnt seem to be breaking even though wrong types have been initialized.
See this (Hello.tsx and index.tsx)
https://codesandbox.io/s/1rnjpwrrj7
Even though the wrong type has been initialized for ‘name’ it still works. Could be due to the editor i’m using but i’m really not too sure.