- Prerequisites
- React + Typescript Starter Kits
- Import React
- Stateless Functional Components
- Stateful Class-based Components
- Extracting Prop Types
- Forms and Events
- Type Troubleshooting Handbook
- My question isn't answered here!
- good understanding of React
- familiarity with Typescript Types
- https://github.com/wmonk/create-react-app-typescript is the officially recommended Typescript fork of
create-react-app
. - https://github.com/sw-yx/create-react-app-parcel works with Typescript out of the box.
- https://github.com/basarat/typescript-react/tree/master/01%20bootstrap for manual setup of React + Typescript + Webpack + Babel
In particular, make sure that you have @types/react
and @types/react-dom
installed. Read more about the DefinitelyTyped project if you are unfamiliar
import * as React from 'react';
import * as ReactDOM from 'react-dom';
You can specify the type of props as you destructure them:
const App = ({ message: string }) => <div>{message}</div>;
Or you can use the provided generic type for functional components:
const App: React.SFC<{ message: string }> = ({ message }) => <div>{message}</div>;
Within Typescript, React.Component
is a generic type (aka React.Component<PropType, StateType>
), so you actually want to provide it with prop and (optionally) state types:
class App extends React.Component<{
message: string, // like this
}> {
render() {
return (
<div>{this.props.message}</div>
);
}
}
If the component has state, here's how to add the types for the state:
class App extends React.Component<{
message: string,
}, {
count: number, // like this
}> {
state = {
count: 0
}
render() {
return (
<div>{this.props.message} {this.state.count}</div>
);
}
}
If you need to define a clickhandler, just do it like normal, but just remember any arguments for your functions also need to be typed:
class App extends React.Component<{
message: string,
}, {
count: number,
}> {
state = {
count: 0
}
render() {
return (
<div onClick={() => this.increment(1)}>{this.props.message} {this.state.count}</div>
);
}
increment = (amt: number) => { // like this
this.setState({
count: this.state.count + amt
});
}
}
If you need to declare class properties for later use, just declare it with a type:
class App extends React.Component<{
message: string,
}> {
pointer: number // like this
componentDidMount() {
this.pointer = 3;
}
render() {
return (
<div>{this.props.message} and {this.pointer}</div>
);
}
}
Instead of defining prop types inline, you can declare them separately (useful for reusability or code organization):
type AppProps = { message: string }
const App: React.SFC<AppProps> = ({ message }) => <div>{message}</div>;
You can also do this for stateful component types (really, any types):
type AppProps = { // like this
message: string,
}
type AppState = { // and this
count: number,
}
class App extends React.Component<AppProps, AppState> {
state = {
count: 0
}
render() {
return (
<div>{this.props.message} {this.state.count}</div>
);
}
}
interface
s are different from type
s in Typescript, but for our purposes they do the same things. read more
This can be a bit tricky. The tooling really comes in handy here, as the @type definitions come with a wealth of typing. Type what you are looking for and usually the autocomplete will help you out. Here is what it looks like for an onChange
for a form event:
class App extends React.Component<{}, { // no props
count: string,
}> {
state = {
text: ''
}
render() {
return (
<div>
<input
type="text"
value={this.state.text}
onChange={this.onChange}
/>
</div>
);
}
onChange = (e: React.FormEvent<HTMLInputElement>): void => {
this.setState({text: e.currentTarget.value})
}
}
Facing weird type errors? You aren't alone. This is the worst part of using Typescript with React. Try to avoid typing with any
as much as possible to experience the full benefits of typescript. Instead, let's try to be familiar with some of the common strategies to solve these issues.
Union types are handy for solving some of these typing problems:
class App extends React.Component<{}, {
count: number | null, // like this
}> {
state = {
count: null
}
render() {
return (
<div onClick={() => this.increment(1)}>{this.state.count}</div>
);
}
increment = (amt: number) => {
this.setState({
count: this.state.count + amt
});
}
}
If a component has an optional prop, add a question mark :) and assign during destructure (or use defaultProps).
class MyComponent extends React.Component<{
message?: string, // like this
}> {
render() {
const {message = 'default'} = this.props;
return (
<div>{message}</div>
);
}
}
You can also use a !
character to assert that something is not undefined, but this is not encouraged.
Sometimes union types need to be cast to a more specific type to work with other APIs, so cast with the as
keyword.
class MyComponent extends React.Component<{
message: string,
}> {
render() {
const {message} = this.props;
return (
<Component2 message={message as SpecialMessageType}>{message}</Component2>
);
}
}