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.
The advantages of using Flow as a static type checker are the following:
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.
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.
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.
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} };
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’;
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.
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.
Check out our newsletter