[001.1] Setting up a React App

Starting a React App, writing tests, and adding Flow.js to it.

Subscribe now

Setting up a React App [05.03.2017]

In the last two episodes, we built a Rails API for a Custom Form Builder. Today we will start a React application that will allow us to display and submit forms generated in that API. We'll start the project using Create React App. This is a boilerplate made by Facebook to rapidly create applications using webpack and a series of scripts to aid development. Let's get started.

Project

The first thing we will do is install create-react-app globally on our machine.

yarn global add create-react-app

Then we will use it to create our app. Our app's name will be formulae_react:

create-react-app formulae_react

This will generate a new React app for us with sensible defaults. We can change into the directory and run yarn start to see the initial app in the browser. By default, it will run on port 3000:

cd formulae_react
yarn start

The entry point for this app is the index.js file. Let's have a look:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

Here it's rendering an App component in the element with id root. Let's look at the App component:

vim src/App.js
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>Welcome to React</h2>
        </div>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

export default App;

This is just a typical React component, rendering some boilerplate. create-react-app also generated a test:

vim src/App.test.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

it('renders without crashing', () => {
  const div = document.createElement('div');
  ReactDOM.render(<App />, div);
});

This is a pretty barebones test - it verifies that the App component renders successfully. Let's run the tests:

yarn test

This ran the test, and now it's in watch mode - when we change a file and save it, the corresponding tests will be run for us here. Let's exit with q.

Adding Flow

Before we move on, we'll be introducing flow to our project. Flow is a static type checker for JavaScript. It will allow us to annotate various expressions, and ensure we're passing the right types of arguments to our functions.

Let's install it:

yarn add --dev flow-bin
vim package.json

We'll add a flow script to our package.json file:

{
  "name": "formulae_react",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^15.5.4",
    "react-dom": "^15.5.4"
  },
  "devDependencies": {
    "react-scripts": "0.9.5"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject",
    "flow": "flow"
  }
}

Then we'll create an initial configuration, with:

yarn flow -- init

This generated a .flowconfig file for us, though it's essentially empty at the moment.

You can also set up your editor for Flow Integration. I highly recommend it.

Creating our components

Now that we have flow set up, let's create some components. We will create a directory for our components to live in:

mkdir src/components

We'll also create a components.js file that will export all the components we will have.

vim src/components.js
import RespondToForm from './components/RespondToForm'

export {
 RespondToForm
}

Our topmost component will be RespondToForm. Let's move App.js to that location and gut it:

mv src/App.js src/components/RespondToForm.js
# we'll move the `App.test.js` file as well
mv src/App.test.js src/components/RespondToForm.test.js
vim src/components/RespondToForm.js

We'll make a component that's a pure function, rather than a class-based stateful component:

import React from "react";

export default function RespondToForm() {
  return <div>something</div>;
}

We'll update the test as well:

vim src/components/RespondToForm.test.js
import React from "react";
import ReactDOM from "react-dom";
import RespondToForm from "./RespondToForm";

it("renders without crashing", () => {
  const div = document.createElement("div");
  ReactDOM.render(<RespondToForm />, div);
});

Then we'll verify the tests still pass:

yarn test

They do. Now we can change the index.js file to load RespondToForm instead of App:

vim src/index.js
import React from "react";
import ReactDOM from "react-dom";
import RespondToForm from "./components/RespondToForm";
import "./index.css";

ReactDOM.render(<RespondToForm />, document.getElementById("root"));

We can check it out in our browser to make sure this worked.

In our model a Form has multiple Sections. Let's create a Section component inside of a RespondToForm directory, since this component only makes sense inside of a RespondToForm component:

mkdir src/components/RespondToForm
vim src/components/RespondToForm/Section.js
import React from 'react'

export default function Section(props) {
  const { title } = props

  return <h2>{title}</h2>
}

Here we're just rendering an h2 that contains whatever's passed in as the title property. Let's create a test for our Section component.

vim src/components/RespondToForm/Section.test.js

Let's write the basic test first:

import React from "react";
import ReactDOM from "react-dom";
import Section from "./Section";

it("renders without crashing", () => {
  const div = document.createElement("div");
  ReactDOM.render(<Section />, div);
});

This is fine as a starting point, but we'd really like to prove to ourselves that it's rendering an h2. We'll introduce Enzyme, which provides us an easy way to render components in our tests and assert things about them. Enzyme provides a shallow function for rendering a single layer of our React application.

We'll install the package as a development dependency. We also need to bring in the react-test-renderer package because enzyme wants it to be available:

yarn add --dev enzyme react-test-renderer

Then we'll update our test to shallow-render our component, and use Enzyme's API to assert that the text() of our rendered component contains the title property we pass in.

import React from 'react'
import ReactDOM from 'react-dom'
import { shallow } from 'enzyme'
import Section from "./Section";

it('renders without crashing', () => {
  const div = document.createElement('div')
  ReactDOM.render(<Section />, div)
})

it('renders the title in an h2', () => {
  const div = document.createElement('div')
  const title = "Second"

  const subject = shallow(<Section title={title} />)

  expect(subject.text()).toMatch(/Second/)
})

Let's run our tests.

yarn test

They pass. So we've got a slightly more interesting test working.

Next, let's introduce flow. To do this, we'll open the RespondToForm file and add a comment at the top that tells flow to check this file:

vim src/components/RespondToForm.js
// @flow
// ... the rest of the file ...

I've got flow integrated into my editor, so I'll see any type errors immediately when I save a file. In this case, there are no errors.

Right now this component doesn't take any arguments. We want to pass it a form prop that could contain multiple sections, and render each section. Let's see what that looks like:

// @flow

import React from "react";
import Section from "./RespondToForm/Section";

export default function RespondToForm(props) {
  const sections = props.form.sections.map(s => <Section title={s.title} />);

  return (
    <div>
      {sections}
    </div>
  );
}

Now when we save the file, flow warns us that we haven't described what type we expect to be passed in as our props variable. We'll define a Props type that looks like an object with a form key containing what we'll call a FormType, which contains a sections key containing an array of what we'll call a SectionType:

// @flow

import React from "react";
import Section from "./Section";

type SectionType = {
  title: string
};

type FormType = {
  sections: Array<SectionType>
};

type Props = {
  form: FormType
};

export default function RespondToForm(props: Props) {
  const sections = props.form.sections.map(s => <Section title={s.title} />);

  return (
    <div>
      {sections}
    </div>
  );
}

Now flow isn't mad any more. If we run the tests now, they'll fail as we're not passing the appropriate props to the component. We'll pass the appropriate shape of data in, then we'll add a test to verify that we produce 2 Section components when we pass 2 sections in:

vim src/components/RespondToForm.test.js
import React from "react";
import ReactDOM from "react-dom";
import RespondToForm from "./RespondToForm";
import Section from "./RespondToForm/Section";
import { shallow } from "enzyme";

it("renders without crashing", () => {
  const div = document.createElement("div");
  ReactDOM.render(<RespondToForm form={{ sections: [] }} />, div);
});

it("renders each section", () => {
  const div = document.createElement("div");
  const form = {
    sections: [{ title: "First" }, { title: "Second" }]
  };

  const subject = shallow(<RespondToForm form={form} />);

  expect(subject.find(Section).length).toBe(2);
});

This passes, but it warns us that each child node needs an independent key. We won't worry about that presently.

Summary

So that's a good starting point for our project. In today's episode, we created a new React application, added Flow to it to help us avoid silly mistakes, and looked at testing. See you soon!

Resources