DEV Community

Vlad Oganov for datarockets

Posted on

Typing React Components with Flow

I’m going to describe how to use Flow in terms of React & Redux. The reason I did this is that this area is not commonly covered. I couldn’t find any best practices or really cool tutorials for using Flow in React & Redux applications. Let’s fix that!

We live in a strange time when almost all programming languages are moving towards static type systems. There are some rumors that Python and Ruby are going to become a static type. And JavaScript is no exception.

There are some options for making JS type safe: TypeScript, Dart, and Flow. I don’t like Dart because of its non-JS appearance. It looks like Java or something similar, but not JS. And of course, it’s not really popular in the JS community.

Another option is TypeScript. In comparison with Flow, in TypeScript, you have to write all of your projects from the beginning, whereas you can apply Flow gradually. And because TypeScript is NOT JavaScript, it cannot follow the ECMAScript standard, and of course, not all libraries are available for TypeScript.

The final option is Flow. It is really amazing! It covers the entire spectrum of typing tools you need, such as type aliases, type inference, type unions, etc.

This article is not for newbies to Flow, because here I focus on React with Flow practices. If you don’t know the basics of Flow, please read my article “ReactJS. Quick Start”, the official Flow docs and then come back to us.

Advantages of using Flow

The advantages of using Flow as a static type checker are the following:

  1. It looks very clear and natural. People with either Haskell or a Swift, Scala, and Kotlin background will find this checker extremely accessible and nice.
  2. It is still JavaScript. We have the same idioms, the same environment, etc.
  3. Your code more reliable. It checks your code at compilation time, not runtime, so that you have feedback about how your code will perform before you run it.
  4. It offers code documentation. You need only one glance to understand what this function wants to accept and what it returns.
  5. It decreases the number of tests. With static type checking, you don’t need to test every function with many conditions and in many contexts to recognize that it doesn’t work like it is supposed to because if it compiles, it probably works. You are recommended to test only high-level APIs such as what a user sees and how a user interacts with your application.

From PropTypes to Props

Currently, react library provides PropTypes for checking the types of props that we pass to a component. That’s cool, but using PropTypes becomes a mess: we have to use the PropTypes namespace and add some strange-looking checkers like PropTypes.oneOf([‘…’]). Also, the main thing is that PropTypes checks your code at runtime, while Flow checks your code before you run it. Check it:

import React, { Component, PropTypes } from ‘react’;

class MyComponent extends Component { 
    static propTypes = {
        label: PropTypes.string,
        status: PropTypes.oneOf(['error', 'fetching', 'ready']),
        items : PropTypes.arrayOf(PropsTypes.string),
        numberOfUsers: PropTypes.number,
        metainfo: PropTypes.shape({
            content: PropTypes.string,
                        userAvatar: PropTypes.string,
        }),
        }
        // cooooode
}

Using Flow, we can clean it up and add more semantics via type aliases and union types. For instance, the status property has a countless number of discrete values, so it’d be better to transform it:

type Status = ‘error’ | ‘fetching’ | ‘ready’;

And now, instead of status: PropTypes.oneOf(['error', 'fetching', 'ready']), we can use status: Status,

We should do the same thing with metainfo too. For this task, we need to type alias the shape of a Metainfo object.

type Metainfo = {
    content: string,
    userAvatar: string,
};

Let’s combine our semantics improvements and Flow syntax in our component. We’d get something like this:

type Status = ‘error’ | ‘fetching’ | ‘ready’;
type Metainfo = {
        content: string,
        userAvatar: string,
};


class MyComponent extends Component { 
    props: {
        label: string,
        status: Status,
        items: Array<string>,
        numberOfUsers: number,
        metainfo: Metainfo,
        }

        // cooooode
}

Pretty concise and clear. One glance and you see what happens.

Pure Components

I hope you know what this is. If not, a small explanation: a pure component is a component without a state or methods inside itself; it is just a pure function that accepts props and returns JSX. Briefly, I like to use this feature with UI things such as buttons, inputs, etc.

The only problem that destroys all the beauty of pure components is PropTypes. We have to make something like this:

MyPureComponent.propTypes = { … }

…or go back to the class declaration. Well, let’s move to Flow. It gives us the ability to create pure components without PropTypes and keep the type safe. I’m gonna show you a comparison example for better understanding. Take a look at this:

import React, { Component, PropTypes } from ‘react’;

class Section extends Component {
    static propTypes = {
        title: PropTypes.string,
                content: PropTypes.string,
                link: PropTypes.string,
        }

        render = () => (
            <div>
           <title>{this.props.title}</title>
           <p>{this.props.content}</p>
           <div>{this.props.link}</div>
                </div>
    ) 
}

Let’s transform it into a pure component using function syntax and Flow:

import React, { Component, PropTypes } from ‘react’;

    type SectionProps = {
                title: string,
                content: string,
                link: string
        };

const Section = ({ title, content, link }: SectionProps) => (
    <div>
        <title>{title}</title>
            <p>{content}</p>
            <div>{link}</div>
        </div>
) ;

Awesome! In my opinion, this looks simple and clear.

Redux (Action Creators, Thunk Action Creators, Reducer)

Action creators are just pure functions that accept something and return an object. To increase safety, we can use types. But this is not the only reason to use Flow; we can add the semantics to it. For instance:

export const fetchedUserProfile = user => ({
        type: ‘fetchedUserProfile’,
        payload: {
            user,
        },
});

Using Flow, we can make our type for the user to check that the user object has the properties we expect. Also, we can do the same for action so that it’ll enforce the convention about what action should look like:

type User = { id: number, name: string, email: string };

And for actions:

type ActionType = ‘error’ | ‘fetchUserProfile’ | ‘fetchedUserProfile’;
type Action = { type: ActionType, payload: Object };

With our new types, the transformation of the fetchedUserProfile function will be the following:

export const fetchedUserProfile = (user: User): Action => ({ … });

Just one glance and you’ll know how to use it. Documentability! 🙂

Reducer is just a function too, so we can add some magic (not) to it via types. A plain reducer:

const defaultState = {
    status: ‘’,
    userProfile: {},
    items: [],
};

const reducer = (state = defaultState, action) => {
    switch(action.type) {
            case ‘’: {}
            default: return state;
    }
};

Add types:

type User = { id: number, name: string, email: string };
    type Items = { id: number, content: string, check: boolean };

    type ActionType = ‘error’ | ‘fetchUserProfile’ | ‘fetchedUserProfile’;
    type Action = { type: ActionType, payload: Object };

    type State = {
        status: ‘error’ | ‘loading’ | ‘ready’,
        userProfile: User, 
        items: Array<Items>,
    };

And our reducer becomes cool and clear:

const defaultState: State = {
    status: ‘’,
    userProfile: {},
    items: [],
};

const reducer = (state: State = defaultState, action:  Action): State => {
    switch(action.type) {
            case ‘’: {}
            default: return state;
    }
};

Meow :3

We’re moving further toward more advanced types of action creators—thunk action creators. Here we can also use types, but it’s more developed than previous cases.

const fetchUserProfile = (userId) => (dispatch) => 
    User
    .load(userId)
    .then(response => dispatch(fetchedUserProfile(response.user)))
    .catch(() => dispatch(fetchingError()));

Are you ready for types? Of course, you are!

type ActionType = ‘error’ | ‘fetchUserProfile’ | ‘fetchedUserProfile’;
type Action = { type: ActionType, payload: Object };

type Dispatch = (action: Action) => void;


const fetchUserProfile = (userId: number) =>
(dispatch: Dispatch): void => 
    User
    .load(userId)
    .then(response => dispatch(fetchedUserProfile(response.user)))
    .catch(() => dispatch(fetchingError()));

I’d recommend you look at some examples of using types with async functions in the official docs. There you’ll find awesome instances of using Flow with asyncs.

Don’t Use Object

In this section, I’d like to go on a tangent and talk about generics. It’s useful to raise a level of abstraction and make boxes around the things with different types.

Do you remember our Action type? No? Me either 🙂 JK

type Action = { type: ActionType, payload: Object };

It isn’t type safe in light of the payload property’s type because we can place every object with whatever signature. The only one that works—unpredictable. How can we solve this problem in terms of Flow? Use disjoint unions. Look at this:

type Action =
{ type: ‘create’, payload: { name: string } }
|  { type: ‘delete’, payload: { id: number } }
|  { type: ‘update’, payload: { id: number, name: string} };

Type library

Move your types to a separate module (js file) so that they can be used in other modules and be the same throughout the entire app. You just need something like this:

// types.js
export type User = { name: string, email: string, id: number | string };

And just import it to another js file.

// actions.js
import type { User } from ‘./types.js’;

Add more semantics

Instead of using types only for checking the reliability of your apps, you should use it to add an explanation of what it is via the type name. Check the following example:

type Password = string;

I think it is more understandable for further use now.

Summary

  1. Use type aliases and union types to add more semantics.
  2. Use pure components.
  3. Use types for class components.
  4. Use types for action creators.

But enough is enough. Don’t type alias everything, and don’t recreate the wheel.
Well, thank you for coming! Love React, use types, and be happy.

Top comments (1)

Collapse
 
mohas profile image
Mohas

I kinda like flow but the reason I could not use it till now is because of tooling, I use vscode and flow for code does not give a very good experience, please talk about your tools