Type checking React Apps with Flow

Introduction

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.

1"scripts": {
2      "flow": "flow",
3    }

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.

1[ignore]
2    .*/node_modules/.*
3    .*/src/registerServiceWorker\.js
4    .*/src/index\.js
5    .*\.test\.js
6    
7    
8    [include]
9    
10    [libs]
11    
12    [lints]
13    
14    [options]
15    all=true
16    
17    [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.

flow-type-check-1

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.

1// src/App.js
2    
3    type TextProps = {
4      
5    }
6    
7    class App extends React.Component<TextProps> {
8      render() {
9        return (
10          <div className="App">
11            <header className="App-header">
12              <img src={logo} className="App-logo" alt="logo" />
13              <h1 className="App-title">Welcome to React</h1>
14            </header>
15          </div>
16        );
17      }
18    }

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.

flow-type-check-2

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.

1// src/App.js
2    
3    type TextProps = {
4      name: string
5    }
6    
7    class Text extends React.Component<TextProps> {
8      render () {
9    
10        return (
11          <React.Fragment>
12            <p>{this.props.name}</p>
13          </React.Fragment>
14        )
15      }
16    }

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.

1// src/App.js
2    
3    type TextProps = {
4      name: string
5    }
6    
7    class Text extends React.Component<TextProps> {
8      render () {
9    
10        return (
11          <React.Fragment>
12            <p>{this.props.name}</p>
13          </React.Fragment>
14        )
15      }
16    }
17    
18    class App extends React.Component<{}> {
19      render() {
20        return (
21          <div className="App">
22            <header className="App-header">
23              <img src={logo} className="App-logo" alt="logo" />
24              <h1 className="App-title">Welcome to React</h1>
25            </header>
26            <Text name={'Yomi'} />
27          </div>
28        );
29      }
30    }

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} />
flow-type-check-3

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.

1// src/App.js
2    
3    type TextProps = {
4      name: string
5    }
6    
7    type State = {
8      count: number,
9    };
10    
11    class Text extends React.Component<TextProps, State> {
12    
13      state = {
14        count: 0,
15      };
16    
17      componentDidMount() {
18        setInterval(() => {
19          this.setState(prevState => ({
20            count: prevState.count + 1,
21          }));
22        }, 1000);
23      }
24    
25      render () {
26    
27        return (
28          <React.Fragment>
29            <p>Count: {this.state.count}</p>
30            <p>{this.props.name}</p>
31          </React.Fragment>
32        )
33      }
34    }

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.

1// src/App.js
2    
3    class EventComponent extends React.Component<{}, { count: number }> {
4      state = {
5        count: 0,
6      };
7      
8      handleClick = (event: SyntheticEvent<HTMLButtonElement>) => {
9        // To access your button instance use `event.currentTarget`.
10        (event.currentTarget: HTMLButtonElement);
11    
12        this.setState(prevState => ({
13          count: prevState.count + 1,
14        }));
15      };
16    
17      render() {
18        return (
19          <div>
20            <p>Count: {this.state.count}</p>
21            <button onClick={this.handleClick}>
22              Increment
23            </button>
24          </div>
25        );
26      }
27    }

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

1// src/App.js
2    
3    class CustomTextInput extends React.Component<{}> {
4    
5      handleSubmit = e => {
6        e.preventDefault();
7        console.log(this.textInput);
8      };
9    
10    
11      // tell Flow that we want to associate the textInput ref
12      // with an HTML Input button
13      textInput: ?HTMLInputElement;
14    
15      render() {
16        return (
17          <div>
18            <form onSubmit={e => this.handleSubmit(e)}>
19              <input type="text" ref={textInput => (this.textInput = textInput)} />
20              <button>Submit</button>
21            </form>
22          </div>
23        );
24      }
25    }

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.

1// src/App.js
2    
3    class CustomTextInput extends React.Component<{}> {
4    
5      handleSubmit = e => {
6        e.preventDefault();
7        console.log(this.textInput);
8      };
9    
10    
11      // tell Flow that we want to associate the textInput ref
12      // with an HTML Input button
13      textInput: ?HTMLInputElement;
14    
15      render() {
16        return (
17          <div>
18            <form onSubmit={e => this.handleSubmit(e)}>
19              <textarea ref={textInput => (this.textInput = textInput)} />
20              <button>Submit</button>
21            </form>
22          </div>
23        );
24      }
25    }
flow-type-check-4

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.