React
const React = require("react");
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!" |
$> 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
^^^^^^^^^^^^^^^^^^^^^^^ React element `Greeter`
11: <Greeter name={null} />; // `name` should be a string
^^^^ null. This type is incompatible with
3: name: React.PropTypes.string.isRequired,
^^^^^^^^^^^^^^^^^^^^^^ string
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!" |
$> 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 |
const Counter = React.createClass({ getInitialState() { return { value: 0, }; }, increment() { this.setState({ value: this.state.value + "oops!", }); }, decrement() { 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> ); }, }); |
$> flow
8: this.setState({
^ call of method `setState`
9: value: this.state.value + "oops!",
^^^^^^^^^^^^^^^^^^^^^^^^^^ string. This type is incompatible with
4: value: 0,
^ number
13: this.setState({
^ call of method `setState`
13: 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.
class Button extends React.Component {
props: {
title: string,
visited: boolean,
onClick: () => void,
};
state: {
display: 'static' | 'hover' | 'active';
};
static defaultProps: { visited: boolean };
onMouseEnter: () => void;
onMouseLeave: () => void;
onMouseDown: () => void;
constructor(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);
}}
/>
);
React.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.
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 />;
You can edit this page on GitHub and send us a pull request!