Type checking React Apps with Flow
You will need Node 8+ and create-react-app installed on your machine. Some knowledge of React development is required.
Type checkers help to identify certain types of problems before you even run your code. They can also improve developer workflow by adding features like auto-completion. It helps to annotate variables, functions, and it helps to detect mistakes early.
In this tutorial, readers will be introduced to type checking, Flow as a type checker, and how to integrate into a React app.
Prerequisites
Before we begin the tutorial, the following bits are needed:
- Some experience with the React library.
- Knowledge of setting up an application with create-react-app.
- Basic knowledge of JavaScript and React
- Node (8)
- npm (5.2.0)
Please ensure you have Node and npm installed before starting the tutorial.
What is type checking?
Type checking means ensuring that the type of a property (variable, object, function, string) in a programming language is being used as it should be. It is the process of verifying and enforcing the constraints of types, and it can occur either at compile time or at runtime. It helps to detect and report errors.
Type checking can be divided into two: static type checking and dynamic type checking.
Static type checking
Static type checking is used in static-typed languages where the type of the variable is known at the compile time. This means that the type of the variable must be declared beforehand.
An advantage of static type checking is that it allows many type errors to be caught early in the development cycle. Static typing usually results in compiled code that executes more quickly because the compiler already knows the exact data types that are in use.
Examples of statically-typed languages include C, C++, C#, and Java.
Dynamic type checking
Dynamic type checking is used in dynamic-typed languages where the type is usually known at runtime. This means that the type of the variable doesn’t need to be explicitly defined.
In a dynamically-typed language, once there are type errors, the program is most likely to fail at runtime. Therefore, dynamic type checking usually results in less optimized code than static type checking, although it does give room for a flexible and fast development experience as it allows you to build without the overhead of thinking about types.
Examples of dynamically-typed languages include JavaScript, Lisp, Lua, Objective-C, and PHP.
Introduction to Flow
Flow is a static type checker for JavaScript apps that aims to find and eliminate problems as you code. Designed by the Facebook team for JavaScript developers, it’s a static type checker that catches common errors in your application before they run.
As opposed to TypeScript, which is also a static type checker, Flow isn’t a programming language. Instead, it acts like a smart linter in the sense that it examines the .js
files and checks for errors.
Integrating Flow in a React app
To get started with creating the React app, We’ll use the create-react-app
package to bootstrap a new project.
npx create-react-app flowchecker
Once the command is done with the installation, you can proceed by navigating into the project folder. You can go ahead and add Flow to the project by running any of the commands below.
npm install --save-dev flow-bin
At the end of the installation, the latest version of Flow will be in your project.
The next thing to do is add Flow to the "scripts"
section of your package.json
so that Flow can be used in the terminal. In the package.json
file, add the code snippet below.
"scripts": {
"flow": "flow",
}
Finally, for the Flow setup, run any of the commands below:
npm run flow init
This will help to create a Flow configuration file that should be committed. The Flow config file helps to determine the files that Flow should work with and what should be ignored. In this case, we’d not like to carry out static checking on node_modules
files so edit the flow config file with the code below.
[ignore]
.*/node_modules/.*
.*/src/registerServiceWorker\.js
.*/src/index\.js
.*\.test\.js
[include]
[libs]
[lints]
[options]
all=true
[strict]
In the options
section, we’re specifying that Flow works on all files with the exception of the files and folders in the ignore
section.
With Flow installed and setup, let’s see how it can be used in a React app and its various APIs. To get started, add the line of code below to the top of the App.js
file as that’s where we’ll be writing most of the code.
//@flow
That simply means notifying Flow that we’d like it to carry out static type checks in this file. Now run npm run flow
in your terminal and you see should see an error like this below.
Components
The default React component isn’t compatible with Flow. Which is why there was an error above. Flow expects the Component property to have at least one type argument.
However, there’s a fix for that. Edit the App
component with the code block below.
// src/App.js
type TextProps = {
}
class App extends React.Component<TextProps> {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
</div>
);
}
}
By setting the argument to TextProps
which is an empty object, we are telling Flow that the component doesn’t need any property. If run the npm run flow
command, you should see 0 errors now.
Now let’s see how components can be created and used in line with Flow rules. Still in the App.js
file, add the code below.
// src/App.js
type TextProps = {
name: string
}
class Text extends React.Component<TextProps> {
render () {
return (
<React.Fragment>
<p>{this.props.name}</p>
</React.Fragment>
)
}
}
A Text
component is created and it’s set to have the type of TextProps
and the props.name
is set to a string type. This means that if we pass any other thing apart from a string, Flow is going to return an error. We can then include the Text
component inside the App
component.
// src/App.js
type TextProps = {
name: string
}
class Text extends React.Component<TextProps> {
render () {
return (
<React.Fragment>
<p>{this.props.name}</p>
</React.Fragment>
)
}
}
class App extends React.Component<{}> {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<Text name={'Yomi'} />
</div>
);
}
}
Run the npm run flow
command and it should return an output of 0 errors
. So as to see Flow type checking in action, let’s see what would happen if a string was not passed. Go ahead to change the name props to be equal to 32.
<Text name={32} />
Set state
Adding a type for state
is straightforward, create a new object type called State
and define the type you want and it can then be passed as an argument to React.Component
. Modify the Text
component with the code block below.
// src/App.js
type TextProps = {
name: string
}
type State = {
count: number,
};
class Text extends React.Component<TextProps, State> {
state = {
count: 0,
};
componentDidMount() {
setInterval(() => {
this.setState(prevState => ({
count: prevState.count + 1,
}));
}, 1000);
}
render () {
return (
<React.Fragment>
<p>Count: {this.state.count}</p>
<p>{this.props.name}</p>
</React.Fragment>
)
}
}
Event handling
To deal with types and event handling, we make use of SyntheticEvent<T>
type. The SyntheticEvent<T>
types all take a single type argument which is the type of the HTML element the event handler was placed on.
// src/App.js
class EventComponent extends React.Component<{}, { count: number }> {
state = {
count: 0,
};
handleClick = (event: SyntheticEvent<HTMLButtonElement>) => {
// To access your button instance use `event.currentTarget`.
(event.currentTarget: HTMLButtonElement);
this.setState(prevState => ({
count: prevState.count + 1,
}));
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.handleClick}>
Increment
</button>
</div>
);
}
}
The EventComponent
can then be placed in the App
component. If we run the npm run flow
command, it should output 0 errors
.
Ref functions
// src/App.js
class CustomTextInput extends React.Component<{}> {
handleSubmit = e => {
e.preventDefault();
console.log(this.textInput);
};
// tell Flow that we want to associate the textInput ref
// with an HTML Input button
textInput: ?HTMLInputElement;
render() {
return (
<div>
<form onSubmit={e => this.handleSubmit(e)}>
<input type="text" ref={textInput => (this.textInput = textInput)} />
<button>Submit</button>
</form>
</div>
);
}
}
Take a look at this CustomTextInput
component above, it’s an example of how to check for ref
types in React. The ref
is created on the input field by using the callback ref
method.
textInput: ?HTMLInputElement
is simply letting Flow that we’d like the ref
to be an HTMLInputElement. The ?
in ?HTMLInputElement
is important because the first argument to ref
will be HTMLInputElement | null
as React will call your [ref](https://facebook.github.io/react/docs/refs-and-the-dom.html#adding-a-ref-to-a-dom-element)
callback with null when the component is unmounted.
Therefore, to correct that, the ?
is needed so that the textInput
property on CustomTextInput
will not be set until React is done with rendering.
If you run the npm run flow
command, the terminal should output 0 errors
which means our types are correct. However, if we were to change things up a bit like below and create the ref on a textarea
, Flow will return an error.
// src/App.js
class CustomTextInput extends React.Component<{}> {
handleSubmit = e => {
e.preventDefault();
console.log(this.textInput);
};
// tell Flow that we want to associate the textInput ref
// with an HTML Input button
textInput: ?HTMLInputElement;
render() {
return (
<div>
<form onSubmit={e => this.handleSubmit(e)}>
<textarea ref={textInput => (this.textInput = textInput)} />
<button>Submit</button>
</form>
</div>
);
}
}
Conclusion
In this tutorial, we identified what type checking is and the different types of type checking, which are: static and dynamic type checking. We went ahead to explore static type checking by using Flow to type check a React app.
We also explored the importance of type checking and how it can be useful to detect bugs and errors early in the development stage.
The codebase for the React app above can be viewed on GitHub.
3 October 2018
by Yomi Eluwande