React

const React = require("react");
const ReactDOM = require("react-dom");

React applications are composed of nested components. As React-based applications grow, these component trees and the dependencies between them become increasingly complex.

Flow’s static analysis makes building large Web apps with React safe by tracking the types of props and state. Flow understands which props are required and also supports default props.

React provides a few different ways to define components:

This document explains how to make strongly-typed components using all of the above styles and includes an example of a higher order component.

The React.createClass factory#

React ships with PropTypes, which verify the props provided to a component. Unlike the static analysis performed by Flow, PropTypes are only checked at runtime. If your testing doesn’t trigger every code path that provides props to a component, you might not notice a type error in your program.

Flow has built-in support for PropTypes. When Flow sees a createClass factory that specifies PropTypes, it is able to statically verify that all elements are constructed using valid props.

1
2
3
4
5
6
7
8
9
10
11
12
const Greeter = React.createClass({
  propTypes: {
    name: React.PropTypes.string.isRequired,
  },
  render() {
    return <p>Hello, {this.props.name}!</p>;
  },
});

<Greeter />; // Missing `name`
<Greeter name={null} />; // `name` should be a string
<Greeter name="World" />; // "Hello, World!"
show Flow output hide Flow output
$> flow
10: <Greeter />; // Missing `name`
    ^^^^^^^^^^^ React element `Greeter`
2:   propTypes: {
     ^^^^^^^^^ property `name`. Property not found in
10: <Greeter />; // Missing `name`
    ^^^^^^^^^^^ props of React element `Greeter`

11: <Greeter name={null} />; // `name` should be a string
    ^^^^^^^^^^^^^^^^^^^^^^^ props of React element `Greeter`. This type is incompatible with
2:   propTypes: {
     ^^^^^^^^^ propTypes of React component

Flow understands when a default value is specified via getDefaultProps and will not error when the prop is not provided.

Note that it’s still a good idea to specify isRequired, even when a default value is provided, to protect against null prop values. React will only use a default value if the prop value is undefined.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const DefaultGreeter = React.createClass({
  propTypes: {
    name: React.PropTypes.string.isRequired,
  },
  getDefaultProps() {
    return {name: "World"};
  },
  render() {
    return <p>Hello, {this.props.name}!</p>;
  },
});

<DefaultGreeter />; // "Hello, World!"
<DefaultGreeter name={null} />; // `name` should still be a string
<DefaultGreeter name="Flow" />; // "Hello, Flow!"
show Flow output hide Flow output
$> flow
14: <DefaultGreeter name={null} />; // `name` should still be a string
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ props of React element `DefaultGreeter`
14: <DefaultGreeter name={null} />; // `name` should still be a string
                          ^^^^ null. This type is incompatible with
6:     return {name: "World"};
                     ^^^^^^^ string

Flow ensures that state reads and writes are consistent with the object returned from getInitialState.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const Counter = React.createClass({
  getInitialState() {
    return {
      value: 0,
    };
  },
  increment() {
    this.setState({
      value: this.state.value + "oops!",
    });
  },
  decrement() {
    // Note: Typo below is intentional
    this.setState({
      valu: this.state.value - 1,
    });
  },
  render() {
    return (
      <div>
        <button onClick={this.increment}>+</button>
        <input type="text" size="2" value={this.state.value} />
        <button onClick={this.decrement}>-</button>
      </div>
    );
  },
});
show Flow output hide Flow output
$> flow
8:     this.setState({
       ^ call of method `setState`
9:       value: this.state.value + "oops!",
                ^^^^^^^^^^^^^^^^^^^^^^^^^^ string. This type is incompatible with
4:       value: 0,
                ^ number

14:     this.setState({
        ^ call of method `setState`
14:     this.setState({
                      ^ property `valu` of object literal. Property not found in
2:   getInitialState() {
                      ^ object literal

Defining components as React.Component subclasses#

While PropTypes are great, they are quite limited. For example, it’s possible to specify that a prop is some kind of function, but not what parameters that function accepts or what kind of value it might return.

Flow has a much richer type system which is able to express those constraints and more. With class-based components, you can specify the components’ props, default props, and state using Flow’s annotation syntax.

type Props = {
  title: string,
  visited: boolean,
  onClick: () => void,
};

class Button extends React.Component {
  props: Props;

  state: {
    display: 'static' | 'hover' | 'active',
  };

  static defaultProps: { visited: boolean };

  onMouseEnter: () => void;
  onMouseLeave: () => void;
  onMouseDown: () => void;

  constructor(props: Props) {
    super(props);
    this.state = {
      display: 'static',
    };

    const setDisplay = display => this.setState({display});

    this.onMouseEnter = () => setDisplay('hover');
    this.onMouseLeave = () => setDisplay('static');
    this.onMouseDown = () => setDisplay('active');
  }

  render() {
    let className = 'button ' + this.state.display;
    if (this.props.visited) {
      className += ' visited';
    }

    return (
      <div className={className}
        onMouseEnter={this.onMouseEnter}
        onMouseLeave={this.onMouseLeave}
        onMouseDown={this.onMouseDown}
        onClick={this.props.onClick}>
        {this.props.title}
      </div>
    );
  }
}
Button.defaultProps = { visited: false };

function renderButton(container: HTMLElement, visited?: boolean) {
  const element = (
    <Button
      title="Click me!"
      visited={visited}
      onClick={() => {
        renderButton(container, true);
      }}
    />
  );
  ReactDOM.render(element, container);
}

Stateless functional components#

Any function that returns a React element can be used as a component class in a JSX expression.

function SayAgain(props: { message: string }) {
  return (
    <div>
      <p>{props.message}</p>
      <p>{props.message}</p>
    </div>
  );
}

<SayAgain message="Echo!" />;
<SayAgain message="Save the whales!" />;

Stateless functional components can specify default props as destructuring with default values.

function Echo({ message, times = 2 }: { message: string, times?: number }) {
  var messages = new Array(times).fill(<p>{message}</p>);

  return (
    <div>
      {messages}
    </div>
  );
}

<Echo message="Helloooo!" />;
<Echo message="Flow rocks!" times={42} />;

Higher-order components#

Occasionally, repeated patterns in React components can be abstracted into functions that transform one component class into another.

In the example below, the HOC loadAsync takes a component that requires some arbitrary config and a promise that will eventually provide that config and returns a component that takes care of loading the data and displaying the component asynchronously.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
function loadAsync<Config>(
  ComposedComponent: ReactClass<Config>,
  configPromise: Promise<Config>,
): ReactClass<{}> {
  return class extends React.Component {
    state: {
      config: ?Config,
      loading: boolean,
    };

    load: () => void;

    constructor(props) {
      super(props);

      this.state = {
        config: null,
        loading: false,
      }

      this.load = () => {
        this.setState({loading: true});
        configPromise.then(config => this.setState({
          loading: false,
          config,
        }));
      }
    }

    render() {
      if (this.state.config == null) {
        let label = this.state.loading ? "Loading..." : "Load";
        return (
          <button disabled={this.state.loading} onClick={this.load}>
            {label}
          </button>
        );
      } else {
        return <ComposedComponent {...this.state.config} />
      }
    }
  }
}

const AsyncGreeter = loadAsync(Greeter, new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve({ name: "Async World" });
  }, 1000);
}));

<AsyncGreeter />;
show Flow output hide Flow output
$> flow
39:         return <ComposedComponent {...this.state.config} />
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ props of React element `ComposedComponent`. Expected object instead of
39:         return <ComposedComponent {...this.state.config} />
                                          ^^^^^^^^^^^^^^^^^ Config

Another common use for HOCs is to wrap a React component in a HOC that takes fewer or different properties. In this example, we will demonstrate how to write a HOC that wraps a component to create a component that expects a subset of the required properties

First, here is a simple React component that has 3 required properties

type TimelyProps = {
  date: Date,
  name: string,
  excited: boolean
};
class Timely extends React.Component<void, TimelyProps, void> {
  render() {
    const hours = this.props.date.getHours();
    const timeOfDay =
      hours > 17 ? 'Evening' : hours > 12 ? 'Afternoon' : 'Morning';

    return (
      <div>
        Good {timeOfDay} {this.props.name} {this.props.excited ? '!' : ''}
      </div>
    );
  }
}

Using Timely will error if you omit any of the 3 properties or if they have the wrong type

1
2
3
4
<Timely />; // Missing all the required props
<Timely name='John' />; // Missing date and excited
<Timely name='John' excited={true} />; // Missing date
<Timely date={new Date()} name='John' excited={true} /> // Ok!
show Flow output hide Flow output
$> flow
1: <Timely />; // Missing all the required props
   ^^^^^^^^^^ React element `Timely`
-18: class Timely extends React.Component<void, TimelyProps, void> {
                                                ^^^^^^^^^^^ property `date`. Property not found in
1: <Timely />; // Missing all the required props
   ^^^^^^^^^^ props of React element `Timely`

1: <Timely />; // Missing all the required props
   ^^^^^^^^^^ React element `Timely`
-18: class Timely extends React.Component<void, TimelyProps, void> {
                                                ^^^^^^^^^^^ property `excited`. Property not found in
1: <Timely />; // Missing all the required props
   ^^^^^^^^^^ props of React element `Timely`

1: <Timely />; // Missing all the required props
   ^^^^^^^^^^ React element `Timely`
-18: class Timely extends React.Component<void, TimelyProps, void> {
                                                ^^^^^^^^^^^ property `name`. Property not found in
1: <Timely />; // Missing all the required props
   ^^^^^^^^^^ props of React element `Timely`

2: <Timely name='John' />; // Missing date and excited
   ^^^^^^^^^^^^^^^^^^^^^^ React element `Timely`
-18: class Timely extends React.Component<void, TimelyProps, void> {
                                                ^^^^^^^^^^^ property `date`. Property not found in
2: <Timely name='John' />; // Missing date and excited
   ^^^^^^^^^^^^^^^^^^^^^^ props of React element `Timely`

2: <Timely name='John' />; // Missing date and excited
   ^^^^^^^^^^^^^^^^^^^^^^ React element `Timely`
-18: class Timely extends React.Component<void, TimelyProps, void> {
                                                ^^^^^^^^^^^ property `excited`. Property not found in
2: <Timely name='John' />; // Missing date and excited
   ^^^^^^^^^^^^^^^^^^^^^^ props of React element `Timely`

3: <Timely name='John' excited={true} />; // Missing date
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React element `Timely`
-18: class Timely extends React.Component<void, TimelyProps, void> {
                                                ^^^^^^^^^^^ property `date`. Property not found in
3: <Timely name='John' excited={true} />; // Missing date
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ props of React element `Timely`

Now let’s say we wanted to wrap Timely in a component that automatically provides the date

declare function injectDate<Props, C: React.Component<*, Props, *>>(
  Komponent: Class<C>
): Class<React.Component<void, $Diff<Props, {date: Date}>, void>>;

const Timeless = injectDate(Timely);

The name and excited properties are still required, but now you can omit date, since it’s automatically provided!

1
2
3
4
5
6
7
8
9
10
<Timeless />; // props not satisfied.
<Timeless excited={true} />; // props not satisfied.
<Timeless name='Sally' />; // props not satisfied.
<Timeless name={1234} excited={true} />; // name must be a string
<Timeless name='Sally' excited={true} />; // This works!

<Timeless
  name='Sally'
  excited={true}
  date={1234} /> // date must still be a Date object
show Flow output hide Flow output
$> flow
1: <Timeless />; // props not satisfied.
   ^^^^^^^^^^^^ React element `Timeless`
-38: class Timely extends React.Component<void, TimelyProps, void> {
                                                ^^^^^^^^^^^ property `excited`. Property not found in
1: <Timeless />; // props not satisfied.
   ^^^^^^^^^^^^ props of React element `Timeless`

1: <Timeless />; // props not satisfied.
   ^^^^^^^^^^^^ React element `Timeless`
-38: class Timely extends React.Component<void, TimelyProps, void> {
                                                ^^^^^^^^^^^ property `name`. Property not found in
1: <Timeless />; // props not satisfied.
   ^^^^^^^^^^^^ props of React element `Timeless`

2: <Timeless excited={true} />; // props not satisfied.
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^ React element `Timeless`
-38: class Timely extends React.Component<void, TimelyProps, void> {
                                                ^^^^^^^^^^^ property `name`. Property not found in
2: <Timeless excited={true} />; // props not satisfied.
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^ props of React element `Timeless`

3: <Timeless name='Sally' />; // props not satisfied.
   ^^^^^^^^^^^^^^^^^^^^^^^^^ React element `Timeless`
-38: class Timely extends React.Component<void, TimelyProps, void> {
                                                ^^^^^^^^^^^ property `excited`. Property not found in
3: <Timeless name='Sally' />; // props not satisfied.
   ^^^^^^^^^^^^^^^^^^^^^^^^^ props of React element `Timeless`

4: <Timeless name={1234} excited={true} />; // name must be a string
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ props of React element `Timeless`. This type is incompatible with
-38: class Timely extends React.Component<void, TimelyProps, void> {
                                                ^^^^^^^^^^^ object type

7: <Timeless
   ^ props of React element `Timeless`
10:   date={1234} /> // date must still be a Date object
            ^^^^ number. This type is incompatible with
-8: ): Class<React.Component<void, $Diff<Props, {date: Date}>, void>>;
                                                       ^^^^ Date

← Prev

You can edit this page on GitHub and send us a pull request!