Building a Modern React.js app with Flow

Written by pedro_19839 | Published 2018/07/20
Tech Story Tags: react | flowtype | javascript | modern-reactjs-app | reactjs-app

TLDRvia the TL;DR App

Photo by Andras Vas on Unsplash

As a Javascript dev, React has been an integral part of my learning experience. I started learning and writing Javascript in October of 2017, primarily in the context of Rails. Needless to say, there was a lot of jQuery involved and the experience was not particularly pleasant. My work with React actually started in React Native, but this article will strictly be for React web. Most of the concepts are applicable, but the presentation semantics will change in Native (the HTML elements must be switched for React Native <View>s <Text>s, <FlatList>, etc.)

What are we building

Today we will be building a Todo List. This article is meant to illustrate the setup of a React app with Babel/Webpack and Flow for type checking.

The finished repo for part one is here: React Todo

In this article we will primarily focus on using Flow types for Components. Some knowledge of React is assumed.

Disclaimer: I will not be going in-depth into configuring webpack. I mostly use a boilerplate configuration from React-Redux Boilerplate, which is a fantastic starting point for production-ready React apps, but not intended for beginners. If you are interested in learning more about Webpack configuration, here is a great resource: How to Develop React.js Apps Fast Using Webpack 4

Why am I doing this

This article will serve as an introduction to Flow types and static type-checking more generally. It is assumed you have some familiarity with React, but a great deal is not necessary.

Getting started

To start coding right away, run this command in your terminal:

Some explanation behind the base configuration:

For this tutorial I am going to take some steps so we can use webpack hot reloading in development. First I copied over my usual webpack config/ and server/ directories, then I copy over the src/ folder from a create-react-appapplication and add a small index.html. (All of these are found in the repo)

I keep a private repo with my preferred base configuration (I didn’t use it here because it includes extras like Redux Saga and reselect that we will be adding in Part 2 of this series) that I clone to bootstrap a new app with these defaults.

Next, I set up my package.json as follows:

I also copy over my personal .eslintrc and the following .babelrc:

Flow Types

To begin development, create a .flowconfig and then start the flow server

Put the following into your .flowconfig to stop flow from type-checking node_modules:

Now start the server using:

$ yarn start

and go to http://localhost:3000 in your browser.

Next, let’s build our first Flow typed component.

Building a list

So our todo list requires some essential functionality:

  • Displays a list of items with completed status (true/false) and some text description
  • Should be able to mark items completed or not
  • Should be able to add new items
  • Should be able to remove items

So where should we begin? Since we are not using redux (yet), we need one of our components to have and keep track of state. I think in this case we are best served tracking state in the root App. So lets change App.js to this:

For now, we’ve just declared our root component: React Apps always load using a “Root” component, generally named App. A Flow-typed component carries the following class signature: class ComponentName extends Component<Props, State> Our root component is not going to receive any Props, and the State type declaration looks like this:

type AppState = {| todos: Array<any> |}

For now, we are using any because we haven't really determined what a Todo will be yet. We're using the pipes {| |} to tell Flow this is the exact shape of AppState - it should not contain any other keys or be missing the todos key.

Also notice the initialization of default state as a property rather than in the constructor.

Let’s go ahead and define the behaviors for updating our App state here as well:

const flipCompleted: (item: any) => * = item => ({...item,completed: !item.completed,})

class App extends PureComponent<any, AppState> {state: AppState = {todos: [],}

addTodo = (todo: any): void => this.setState(state => ({todos: state.todos.concat(todo),}))

removeTodo = (todoID: number): void => this.setState(state => ({todos: state.todos.filter((todo: any): boolean => todo.id !== todoID)}))

completeTodo = (todo: any): void => {const updateTodos = existing => (existing.id === todo.id? flipCompleted(todo): existing)const newTodosState: (state: AppState) => AppState = state => ({todos: state.todos.map(updateTodos)})

this.setState(newTodosState)  

}

render() {const { todos } = this.state

return (  
  <div className="App">  
    <span className="status-text">  
      Total: {todos.length}  
    </span>  
  </div>  
)  

}}

These are a little weird looking, so I’ll explain each one:

addTodo:

Our addTodo function just accepts a todo item and updates state to include it. the type signature (todo: any): void lets us know we shouldn't expect a return value when we call this.addTodo(todo).

removeTodo:

Remove todo is going to accept a todo ID and iterate over the array of todos to remove the matching ID. Not the most performant solution, but we aren’t building for speed yet.

completeTodo:

Complete todo is going to accept an entire todo item and create a new todo item with its completed property flipped from true => false and vice versa (completed: !todo.completed) and then replace the item in the list of todos.

A note on the type signature:

type UpdateFunction = (item: any) => *

A * is what’s known in Flow as the Existential Type. This is a placeholder type. It is similar to any, but allows you to maintain a certain amount of type-safety, by telling the type checker to infer the returned type, as opposed to any which Flow will ignore altogether. A related concept is Generic types, which we will go over later.

Our removeTodo and completeTodo functions have some issues. Both removeTodo and completeTodo have dependencies on the Todo item having an id property and in the case of completeTodo a completed property. Instead of defining the TodoType here, let's go ahead and start building our TodoList component.

To build a List

Let’s make a directory in src/components/:

Let’s start with a single Todo and then work our way up to the logic we need for the list.

TodoList/TodoItem.js

In here we’ve finally defined our TodoType:

Notice we’re again using pipes to tell Flow this is the exact shape of a TodoType. Here we also define our presentation logic for Todos, like connecting the ability to remove one to a button, or associating a checkbox with the completedproperty and having it handle this.onCompleteTodo:

As far as onCompleteTodo, we're going to call the function passed in by props with the todo itself as the argument. To make sure the function passed into props has the right signature, we defined our TodoItem's prop types:

Here we declare that we want our TodoItem component to receive 3 props, and none of them can be undefined:

  • todo: an object of TodoType shape
  • onCompleted: a function that accepted a TodoType object as an argument, with a void return type
  • onRemove: a function with no required argument (more on this later) with a void return type

To clarify the onRemove function we'll look at the implementation of our TodoList:

TodoList/TodoList.js

Ok, let’s go through the props our List wants to receive first:

Looks familiar, right? the props here correspond directly with the state/behavior we defined in our Root component. The component that maintains state is generally referred to as a container component. Since we only have one in this app i haven't bothered, but generally good practice is to separate stateful containers and presentational components into different directories. In any case, this component is going to receive the list of todos and the behaviors associated with manipulating it as props.

import type { TodoType } from './TodoItem' We import our TodoType (using import type so the dependency is stripped by babel, in cases where the only dependency is on type definitions)

This function probably makes more sense in the form component itself, but it could be argued that the logic of collecting the data that corresponds to an item shouldn’t be coupled to the shape of the item or the way you persist it. In any case, this accepts some text as a todo item, assigns it an ID (the current date as milliseconds since epoch) and gives it the default state of completed: false. This function gets called here:

A few notes: compose: a function that accepts any number of (unary) functions, and returns a function that is the result of composing the function returns from right to left. so in this case,

It may look a little strange, but composition is a very powerful concept. the shorthand

works because in javascript functions are first class. Read more about functional programming in javascript in this excellent series: Composing Software: An Introduction

onRemoveTodo also has some functional flavor, courtesy of currying:

Remember that onRemove prop we referenced in the TodoItem? Well it doesn't need any arguments because the event handler we passed in was a curried function that we defined here. when we render a todo item we pass this function as this.onRemoveTodo(todo.id) and the return value (that the component receives as a prop) is a function with this signature () => void. Because the function retains lexical scope, the todoID we pass in the definition here will be the value this.props.onRemoveTodo(todoID) receives.

This means our renderList function looks like this:

We take each todo in the Array<TodoType>, and create a Todo with the required props.

the reason I used a curried function for onRemove is so the onClick handler in checkbox could be defined point-free (onClick={this.props.onRemove}). This same strategy can be used to refactor our TodoItem into a stateless functional component, by currying onCompleted:

Finally, our render function simply invokes the renderTodos function as well as placing our NewTodoForm above the list for easy access.

TodoList/TodoForm.js

Component props/state:

This is what’s known as a controlled input, meaning the state of the input field is controlled by our application, rather than accepting the user’s input and directly assigning it to the value attribute of the input element in the DOM. This allows you to intercept the value, reformat it, validate, or do whatever you want. In our case we aren't doing anything but saving it in state. This allows us to easily clear the input when the user submits by setting the state field to an empty string:

Because we de-coupled our todo item logic from our form, our form isn’t responsible for building the todo. it simply calls the onSubmit function with the text from the form. If you wanted to add some validation for the form, you would have the option of doing so in the passed in onSubmit. It isn't perfect, but it gets the job done here (if you wanted to validate this form, it'd probably be best to use a validate function as a prop that the form could call before deciding to call onSubmitand clear its state).

TodoList/index.js

Here we are just exposing our TodoList as the default export and defining a type export for TodoType (so our App component can use it).

Now our TodoList is assembled, it’s time to use it in our App component. We can also update our type signatures:

On our way out I decided to spice things up with a custom count function. It simply accepts an array of generic objects and a predicate function, and returns the number of elements for which the function returns true.

This lets us display the count of the completed items as well as the total.

Some notes on the Flow types used here:

type Predicate<T> = (item: T) => boolean

In this type declaration, T is a generic type. When we define our count function, we use another generic type this way:

const count = <K>(list: Array<K>, predicate: Predicate<K>): number => list.reduce((acc, item: K): number => (predicate(item) ? acc + 1 : acc),0,)

So this is saying:

A Predicate<T> is a function that accepts an (item: T) and returns a booleancount<K> is a function that accepts (list: Array<K>, predicate: Predicate<K>), and returns a number

Generic types are a way to describe function behavior in static type checking systems. It uses what’s called a type argument to indicate the actual type will be provided on invocation. Flow can use this definition and understand that if count is called with an Array of K, the Predicate function needs to accept an item of type K as an argument, and return a boolean.

This helps Flow to understand what you want your code to do, so later on if I try this:

// in some other filetype Stuff = { item: string }const arrayOfStuff: Array<Stuff> = [{ item: 'hey' }, { item: 'hi' }]export default arrayOfStuff

// in yet another fileimport array from './someOtherFile'import count from './App.js'

count(array, thing => !!thing.notItem)// cannot get thing.notItem because property notItem is missing in 'Stuff'

Flow will let me know this code I’m trying to type makes no sense.

Otherwise here we are simply adding the call to our new TodoList component and passing in the props it expects.

Finished, for now

I hope I have illustrated some of the benefits of a static type-checking system for development. While this is hardly a comprehensive guide, it should serve to introduce some of the basic and important concepts of static types, and (hopefully) provide reason to use a type-checker for your JS projects.


Published by HackerNoon on 2018/07/20