Turn your manual testers into automation experts! Request a DemoStart testRigor Free

How to Write Better JavaScript with Flow: a Simple Guide

Why Use Flow?

Developers and support engineers are spending thousands of work hours debugging code. Even the most straightforward bugs like passing incorrect data types such as passing ‘2’ instead of 2 (i.e., string instead of a number) or incorrectly passing arguments to a function could break a real-time application. The worst part is that debugging such a simple bug in thousands of lines of code distributed across several tightly coupled files could take hours. A developer could insert several bugs in his code without realizing it, especially in a dynamically typed language like JavaScript, with its weak typing system and the JavaScript type coercion.

That’s why it helps to learn some practical ways to write better JavaScript. Using TypeScript or implementing modern JavaScript features like async, await, or arrow functions helps. But still, the very first step should be to start with the fundamental: the data types.

While coercing variables into different types, we can end up with a whole class of bugs in JavaScript that don’t exist in statically typed languages like C, C++, Go, Scala, Rust, and Java.

The main advantage of a statically typed language is that the compiler handles all kinds of checks, and therefore a lot of trivial bugs are caught at a very early stage.

To achieve this in JavaScript, Facebook has introduced Flow – a static type checker for JavaScript. Flow handles type errors effectively and very quickly without changing the actual code. It helps developers code faster and smarter. With Flow, a developer can feel more confident about his code. Flow checks the code for errors through static type annotations.

In this article, we will go through Flow and its uses. We will start by setting up Flow, and we will see how Flow helps us code in JavaScript.

Setting Up Flow

Before installing Flow, first, we need to set up a compiler. You can use any of the flow-remove-types or Babel to work with Flow. In this guide, we will use Babel: it will strip out all types of annotations from the Flow code.

To install Babel with npm, you can use the following snippet:
npm install --save-dev @babel/core @babel/cli @babel/preset-flow
Install Flow with the following:
npm install --save-dev flow-bin
Add a "flow" script to your package.json
{
  "name": "my-flow-project",
  "version": "1.0.0",
  "devDependencies": {
    "flow-bin": "^0.178.0"
  },
  "scripts": {
    "flow": "flow"
  }
}

Running Flow

While running for the first time after installation, we must use init. This will generate a configuration file in the project folder and name it as .flowconfig
npm run flow init
We can now run ad-hoc code checks across the project folder, including all subfolders, by running the following command in the terminal:
npm run flow check

Even though it works fine, there is a downside: every time the developer adds new code or changes a file, Flow rechecks the entire project’s file structure. So it’s not the most efficient to use Flow like this. Instead, we can use the Flow server.

With the Flow server enabled, instead of checking the entire project folder, the Flow server only checks the part that has changed.

You can use the following command to start the server:
npm run flow.
  • The server will start during the first run, and initial test results will be displayed. From the next run, well notice that the workflow will get incrementally faster.
  • Whenever you want to know the test results, use the flow command in the terminal.
  • Once you’re done coding for the day, do not forget to stop the server with npm run flow stop.
  • You can opt in specific files for type checking by adding comment @flow in the beginning of a JS file. This is extremely useful when working on a project with multiple JS files. You can decorate each file with @flow as a comment and debug one file at a time.

How Type Inference Works

We can do type checking in two ways. First one is to specify the expected data type in the code by using annotations. The type checker evaluates the code accordingly. The drawback is that writing annotations can be tedious; and this extra code is only applicable during development. It will get removed from the final JavaScript build at the time of loading by the browser. That’s why code inference is the preferred way of type checking according to most developers. In this case, we use an intelligent tool that can automatically infer the desired data type of a given variable by understanding the context in the code via code interference.

Besides, in this case, we are testing code without any modifications by saving a lot of time for developers . The Type inference is the most appreciated feature of Flow.

Lets looks at some real-world scenarios of using Flow.

Case 1

/*@flow*/

function foo(x) {
  return x.split(' ');
}

foo(78);

In this case, function foo expects a string as an argument, but instead we just passed a number.

By running npm run flow we can easily catch that mistake.
index.js:4

4: return x.split(' ');
            ^^^^^ property `split`. Property not found in
4: return x.split(' ');
          ^ Number

We can clearly see the number of the line as well as the error. All that is left now is to proceed and modify the number to a string.

Case 2

Nullable Types

Flow doesn’t ignore null. It treats it differently compared to other types. So it catches all the errors where null is passed instead of some other valid data types.

Let’s check this in the code:
/*@flow*/

function stringSlice(str) {
  return str.slice(7);
}

var length = stringSlice(null);
This code will result in an error and we need to handle null in an if block:
/*@flow*/

function stringSlice (str) {
  if (str !== null) {
    return str.slice(7);
  }

  return “NA”;
}

var length = stringSlice(null);

Case 3

Library Definitions

We will be using a number of third-party libraries while coding. Flow will throw false alerts in this case, which can be distracting. Usually third party libraries are well tested, and end-users are not expected to touch them.

We can create a libdef i.e a library definition to prevent this issue. It contains declarations of the methods / functions of the third-party code.

Let’s see an example to better understand what we’re discussing:
/* @flow */

var users = [
  { user: 'Joey', designation: 'Actor' },
  { user: 'Drake', designation: 'Doctor' }
];

function getDoctor() {
  return _.findWhere(users, {designation: ''Doctor''});
}
We will run into the following error:
interfaces/app.js:9

9: return _.findWhere(users, {designation: 'developer'});
          ^ identifier `_`. Could not resolve user

So to fix this issue we need to bring in a libdef for underscore. You can also use flow-typed which contains libdef files for most third-party libraries. Use npm install -g flow-typed to set this up.

Conclusion

Now we know how to write better JavaScript with Flow. We have gone through the various type-checking features of Flow and how useful it is to improve the quality of our code.

If you’re also looking into how to improve your testing by streamlining the QA process and adopting a codeless test automation tool – feel free to check out testRigor by registering a free account. Our customers create tests up to 15x faster than other automation tools, and spend almost no time on test maintenance.