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
Section
s. 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!
Comments for Setting up a React App