Skip to content
master
Go to file
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
 
 
 
 
src
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

Setup React App

Build Status Coverage Status

React app made easy 😎

Setup React App is a React & Redux boilerplate with best practices bootstrapped with Create React App. It can save you a lot of time and energy searching for highly scalable solutions with a nice development experience to get started.

Table of Contents

Overview

Getting Started

Development Workflow

Ecosystem

Boilerplate & Skeleton

Enhancement

Deployment & Automated Tasks

Appendix

Introduction

Setting up a React & Redux app from scratch can be a daunting task for beginners. Lots of libraries to install, lots of configuration and other tasks to do to get a project up and running smoothly.

Setup React App can save you from time-consuming tasks. You simply clone the project or follow our guidelines to setup your own development workflow from scratch.

Back to top

Objective

The purpose of this project is to demonstrate how to setup a React & Redux application with everything you need for a comfortable development workflow, including type checking, code linting, testing, task automation, production optimizations, and more.

Back to top

What is Included?

Your project should have a good starting point with a nice development experience you need to build a modern React & Redux app:

  • All features ship with Create React App.
  • Full-featured development workflow:
    • Language extras beyond JSX and ES6/ES7.
    • A live development server with real time module replacement.
    • Task automation and Git pre-commit hooks.
    • Delightful JavaScript and React testing.
    • Code linting and formatting.
    • Static type checking for JavaScript.
    • CSS processing, transforming, and CSS modules.
    • Lazy-load with code-splitting.
    • Continuous Integration and Continuous Deployment.
    • Editor integration and syntax support.
  • React & Redux ecosystem:
    • A state management library.
    • Declarative routing for React.
    • Front-end component library and definitive icon set.
  • Boilerplate and skeleton:
    • Extensible and scalable React & Redux structure.
    • Standard CSS architecture with best practices.
    • A minimal boilerplate for React & Redux.
  • Detailed guidelines and live demo.

Back to top

Prerequisites

Tools

Before getting started, you are required to install the following tools on your machine:

Git

Git is a version control system (VCS) for tracking changes in source files and coordinating work on those files among multiple people. It is primarily used for source code management (SCM) in software development.

Node Version Manager

nvm helps you manage and switch between different Node versions with ease. It provides a command line interface where you can install different versions with a single command, set a default, switch between them and much more.

Node.js

Node.js is an open source, cross-platform JavaScript runtime environment. It uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.

Yarn

Yarn is a package manager for JavaScript. It’s mainly a replacement for package management in your web projects, with a few features missing from the npm CLI.

Visual Studio Code

Visual Studio Code is an open-source code editor that can be run on multiple platforms. It provides support for debugging, built-in terminal and version control, syntax highlights, snippets, and much more.

Services

Below is a list of helpful online services that can make your life easier:

GitHub

GitHub is a web-based hosting service for version control using Git. Using GitHub has numerous benefits including easier collaboration with colleagues and peers, ability to look back on previous versions, and tons of easy integration options.

Travis CI *

Travis CI is a hosted, distributed continuous integration service used to build and test software projects hosted at GitHub.

Coveralls *

Coveralls is a hosted analysis tool, providing statistics about your code coverage.

Heroku *

Heroku is a container-based cloud Platform as a Service (PaaS). The platform is elegant, flexible, and easy to use, offering developers the simplest path to getting their apps to market.

* optional, but will be required in latter sections.

The usage of those tools and services will be explained in following sections.

Back to top

Quick Start

In your working directory, clone Setup React App from GitHub:

git clone https://github.com/rxseven/setup-react-app.git

And run the following commands to get the app up and running:

cd setup-react-app
nvm use
yarn install
yarn start

Then open http://localhost:3000 to view the app in your browser.

Back to top

Setting up a Project

Creating a new project

In your working directory (the directory where you keep your projects e.g. ~/projects), let’s create a new project by running a command:

mkdir setup-react-app

Then, change the current working directory to your local project:

cd setup-react-app

Setting up Git

When it comes to version control, everybody is talking about Git and GitHub these days. Let’s embrace them!

Initializing Git repository

In the project’s root directory, create an empty Git repository by running a command:

git init

Then, the output should look like this:

Initialized empty Git repository in ~/setup-react-app/.git/

For more information, see Git - git-init Documentation

Specifying your name and email address

You may need to specify your name and email address to be recorded in any newly created commits.

In the project’s root directory, run the following commands to set your name and email address on a per-project basis:

git config user.name "Theerawat Pongsupawat"
git config user.email "me@mail.com"

Git will then store your settings in .git/config file inside your project, and the content may look like this:

[user]
  name = Theerawat Pongsupawat
  email = me@mail.com

To verify your settings, run the commands below:

git config user.name
git config user.email

Then, the output should look like this:

Theerawat Pongsupawat
me@mail.com

For more information, see Git - git-config Documentation

Specifying intentionally untracked files to ignore

A .gitignore file specifies intentionally untracked files that Git should ignore. Files already tracked by Git are not affected. For more information, see Git - gitignore Documentation and Ignoring files.

In the project’s root directory, run the command below:

touch .gitignore

commit: Initial commit

Adding README

You can add a README file to your repository to tell other developers why your project is useful, what they can do with your project, and how they can use it. For more information see, About READMEs.

In the project’s root directory, create a README file by running a command:

touch README.md

Then, add the content below:

# Setup React App

commit: Add README

Adding a LICENSE

GitHub created choosealicense.com, to help you understand how to license your code. A software license tells others what they can and can’t do with your source code, so it’s important to make an informed decision.

You’re under no obligation to choose a license. However, without a license, the default copyright laws apply, meaning that you retain all rights to your source code and no one may reproduce, distribute, or create derivative works from your work.

If you include a detectable license in your GitHub repository, people who visit your repository will see it at the top of the repository page. For more information on adding a license to a repository, see Adding a license to a repository on GitHub Help.

Setup React App is open source software licensed as MIT. Open source licenses enable others to freely use, change, and distribute the project in your repository.

commit: Add LICENSE

Adding an existing project to GitHub

First, we need to create a new remote repository on GitHub. To do that, head to the relevant GitHub documentation page and follow the instructions.

Open your GitHub project page, click on green colored Clone or download button and copy the remote repository URL.

In the project’s root directory, add the URL for the remote repository where your local repository will be pushed by runnig the following command:

git remote add origin https://github.com/rxseven/setup-react-app.git

To verify that a remote repository named origin is set for your project successfully, run the command below:

git remote -v

Then, the output should look like this:

origin	https://github.com/rxseven/setup-react-app.git (fetch)
origin	https://github.com/rxseven/setup-react-app.git (push)

Now, you can publish a local branch to a remote repository on GitHub:

git push -u origin master

Setting up Git workflows

A Git workflow is a recipe or recommendation for how to use Git to accomplish work in a consistent and productive manner. Details are available in this article.

Creating develop branch

A develop branch must branch off from the master branch:

git checkout -b develop

Once a new branch is created, you should publish it to a remote repository:

git push -u origin develop

Creating a feature branch

When starting work on a new feature, branch off from the develop branch:

git checkout -b feature/setup-project

Once a new branch is created, you should publish it to a remote repository:

git push -u origin feature/setup-project

Specifying a Node version

nvm helps you manage and switch between different Node versions with ease. It allows you to specify a Node version on a per-project basis.

How it works

By creating a .nvmrc file inside a project and specify a version number, run nvm use command, nvm will then read the contents of the .nvmrc file and use whatever version of Node you specify.

Configuration

On the command line, run a single command from the project’s root directory to create a configuration file:

touch .nvmrc

And specify a Node version number to .nvmrc file:

echo "8.9.3" > .nvmrc

Now, your .nvmrc file should contain a Node version number like 8.9.3.

Note: the contents of a .nvmrc file must be the <version> (as described by nvm --help) followed by a newline. No trailing spaces are allowed, and the trailing newline is required.

commit: Specify Node version on a per-project basis

Usage

From the project’s root directory, run a simple command:

nvm use

Then, the output should look like this:

Found '~/setup-react-app/.nvmrc' with version <8.9.3>
Now using node v8.9.3 (npm v5.5.1)

Back to top

Setting up a Code Editor

Visual Studio Code provides developers with many awesome features that significantly facilitate the process of source code editing. Besides, It also makes sure users won’t be bored when they work with it, as it allows them to customize several parts of its appearance and functionalities, such as themes, fonts, validaton rules, extensions etc.

Configuring a workspace

Visual Studio Code allows you to customize a Workspace Settings on a per-project basis with a pretty straightforward and intuitive way to quickly add your customizations.

You can do this by editing configuration file in JSON format. First, let’s create .vscode folder in the project’s root directory:

mkdir .vscode

Then, create a configuration file inside that folder:

touch .vscode/settings.json

For more information, see User and Workspace Settings.

commit: Add VSCode's workspace settings

Enabling syntax highlighting

To enable the syntax highlighting in Visual Studio Code, you need to install Babel ES6/ES7 extension:

  1. Open Command Palette in Visial Studio Code by pressing command + p.
  2. Type ext install dzannotti.vscode-babel-coloring and hit enter.
  3. Reload Visual Studio Code.

Disabling built-in code formatter and validator

Visual Studio Code has built-in code formatter and validator which is useful for beginners. In the latter section we will be using a better solution which can take your development workflow to the next level.

For the time being, let’s disable the built-in tools through Visual Studio Code’s workspace settings:

  1. In Visual Studio Code, open a Settings screen by pressing command + ,.
  2. Select Workspace Settings tab.
  3. And add the following configuration to the empty configuration object:
{
  // Disable built-in code formatter and validator
  "editor.formatOnSave": false,
  "javascript.format.enable": false,
  "javascript.validate.enable": false
}

commit: Disable built-in VSCode's code formatter and validator

Back to top

Creating a React App

Creating an app

With Create React App, you don’t need to install or configure tools like Webpack or Babel. They are preconfigured so that you can focus on the code. Just create a project, and you’re good to go.

But to create a better development experience with best practices, in following sections we will customize Create React App to enhance our development workflow in an efficient way.

First, let’s create a new app with Create React App by running a single command from the project’s root directory:

npx create-react-app setup-react-app

Note: npx comes with npm 5.2+ and higher, with npx you don’t need to install create-react-app globally.

Now, your project structure should look like this:

setup-react-app
├── .git
├── .vscode
│   ├── launch.json
│   └── settings.json
├── setup-react-app
└── .nvmrc

We need to move all files and folders from setup-react-app (sub-folder) up one level. In the project’s root directory:

cd setup-react-app && mv {.[!.],}* ../ && cd .. && rmdir setup-react-app

commit: Create React app with CRA

Extracting hidden configuration

Ejecting lets you customize anything, but from that point on you have to maintain the configuration and scripts yourself.

yarn eject

This command will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them.

After ejecting, these files and folders will be adding to your project:

  setup-react-app/
+   config/
+     jest/
+       cssTransform.js
+       fileTransform.js
+     env.js
+     paths.js
+     polyfills.js
+     webpack.config.dev.js
+     webpack.config.prod.js
+     webpackDevServer.config.js
+   scripts/
+     build.js
+     start.js
+     test.js

Also package.json file will be updating with the following changes:

  {
    ...
    ...
    "dependencies": {
+     ...
-     "react-scripts": "1.1.4"
+     ...
    }
    "scripts": {
-     "start": "react-scripts start",
-     "build": "react-scripts build",
-     "test": "react-scripts test --env=jsdom",
-     "eject": "react-scripts eject"
+     "start": "node scripts/start.js",
+     "build": "node scripts/build.js",
+     "test": "node scripts/test.js --env=jsdom"
-   }
+   },
+   "jest": {...},
+   "babel": {...},
+   "eslintConfig": {...}
  }

And these files must be checked into your repository.

commit: Extract hidden configuration from CRA

Then, we need to update Yarn lockfile:

yarn install

commit: Update Yarn lockfile

Configuring Babel

To make package.json file clean and concise, we will be moving babel section to its own configuration file.

In the project’s root directory, create a configuration file:

touch .babelrc

Open package.json file, cut babel property and its values:

  {
-   "babel": {
-     "presets": [
-       "react-app"
-     ]
-   }
  }

In .babelrc file, paste code snippets from a clipboard and remove babel key:

{
  "presets": ["react-app"]
}

commit: Settup Babel

You may need to define .babelrc as a JSON format to Visual Studio Code’s file association list. In .vscode/settings.json file, add the following configuration:

{
  // File associations to languages
  "files.associations": {
    ".babelrc": "json"
  }
}

commit: Add Babel configuration to VSCode's file association list

Cleaning up app boilerplate

Let’s remove these files generated by Create React App:

  setup-react-app
    src
-     App.css
-     App.js
-     App.test.js
-     logo.svg
-     index.css

In the project’s root directory, run the command below:

cd src && rm App.css App.js App.test.js logo.svg index.css

Since those files have been removed, now the app is broken, so we need to fix it by updating src/index.js file (project starting point) with the following changes:

+ // Module dependencies
  import React from 'react';
  import ReactDOM from 'react-dom';
- import './index.css';
- import App from './App';
+
  import registerServiceWorker from './registerServiceWorker';

+ // Render React element into the DOM
- ReactDOM.render(<App />, document.getElementById('root'));
+ ReactDOM.render(<h1>Welcome to React</h1>, document.getElementById('root'));
+
+ // Service worker
  registerServiceWorker();

Now, your neat folder structure should look like this:

setup-react-app
├── node_modules
├── public
│   ├── favicon.ico
│   ├── index.html
│   └── manifest.json
├── src
│   ├── index.js
│   └── registerServiceWorker.js
├── .gitignore
├── package.json
├── README.md
└── yarn.lock

commit: Clean up app boilerplate generated by CRA

Categorizing module dependencies

Module dependencies for development and production listed in package.json file must be categorized as devDependencies and dependencies respectively:

{
  "dependencies": {
    "chalk": "",
    "dotenv": "",
    "dotenv-expand": "",
    "fs-extra": "",
    "object-assign": "",
    "promise": "",
    "raf": "",
    "react": "",
    "react-dom": "",
    "resolve": "",
    "whatwg-fetch": ""
  },
  "devDependencies": {
    "autoprefixer": "",
    "babel-core": "",
    "babel-eslint": "",
    "babel-jest": "",
    "babel-loader": "",
    "babel-preset-react-app": "",
    "babel-runtime": "",
    "case-sensitive-paths-webpack-plugin": "",
    "css-loader": "",
    "eslint": "",
    "eslint-config-react-app": "",
    "eslint-loader": "",
    "eslint-plugin-flowtype": "",
    "eslint-plugin-import": "",
    "eslint-plugin-jsx-a11y": "",
    "eslint-plugin-react": "",
    "extract-text-webpack-plugin": "",
    "file-loader": "",
    "html-webpack-plugin": "",
    "jest": "",
    "postcss-flexbugs-fixes": "",
    "postcss-loader": "",
    "react-dev-utils": "",
    "style-loader": "",
    "sw-precache-webpack-plugin": "",
    "url-loader": "",
    "webpack": "",
    "webpack-dev-server": "",
    "webpack-manifest-plugin": ""
  }
}

commit: Categorize module dependencies

Adding app information

package.json

In the project’s root directory, open package.json file and update with the changes below:

  {
    ...
    ...
+   "description": "React app made easy.",
    "version": "0.1.0",
+   "license": "MIT",
+   "author": {
+     "name": "Theerawat Pongpawat",
+     "email": "rxseven.com@gmail.com",
+     "url": "http://www.rxseven.com"
+   },
+   "repository": {
+     "type": "git",
+     "url": "https://github.com/rxseven/setup-react-app.git"
+   },
    "private": true,
+   "main": "index.js",
    ...
    ...
  }

commit: Add app information to package.json file

Important note on homepage field in package.json: see deployment section.

manifest.json

Open public/manifest.json file and update with the changes below:

  {
    "short_name": "React App",
-   "name": "Create React App Sample",
+   "name": "Setup React App",
    ...
    ...
  }

commit: Add app information to manifest.json file

Cleaning up scripts

scripts section in package.json file also needs to be refactored. First, let’s reorder its list items alphabetically:

{
  "scripts": {
    "build": "node scripts/build.js",
    "start": "node scripts/start.js",
    "test": "node scripts/test.js --env=jsdom"
  }
}

Then, move scripts section over dependency lists.

commit: Reorder scripts in package.json alphabetically

Specifying the app title

Open public/index.html file and update <title> element:

- <title>React App</title>
+ <title>Setup React App</title>

commit: Specify app title to HTML document

Changing the app favicon

Replace public/favicon.io with new images:

16px @1x (Standard)
24px
32px @2x (Retina display)
64px @4x

commit: Change app favicon

Creating a scalable folder structure

To create a scalable folder structure, in the project’s root directory, run a couple of commands below:

cd src
mkdir components config constants dependencies helpers HOCs images screens tests
cd components && mkdir common core && cd ..
cd helpers && mkdir __tests__ && cd ..
cd HOCs && mkdir common && cd ..
cd images && mkdir common && cd ..
cd screens && mkdir main && cd ..

Now, your folder structure should look like this:

src
├── components
│   ├── common
│   └── core
├── config
├── constants
├── dependencies
├── helpers
│   └── __tests__
├── HOCs
│   └── common
├── images
│   └── common
├── screens
│   └── main
└── tests

Changing file extension

Essentially, .js should only ever be for files that use features of standard JavaScript - anything non-standard, like JSX, Flow, TypeScript, or early-stage proposals, needs a different extension. For convenience, we will be using .jsx with React components rather than .js. For more information, see this discussion.

First, we need to change the extension of the project starting point from .js to .jsx by running the following command:

mv src/index.js src/index.jsx

Then, update the appIndexJs property in config/paths.js file to point to src/index.jsx:

  module.exports = {
    ...
-   appIndexJs: resolveApp('src/index.js'),
+   appIndexJs: resolveApp('src/index.jsx'),
    ...
  };

We also need to update main section in package.json file as the following:

  {
    ...
    "private": true,
-   "main": "index.js",
+   "main": "index.jsx",
    "dependencies": {
    ...
  }

commit: Change project starting point's file extension

Creating a starting point component

On the command line, create App folder inside src/components/core:

mkdir src/components/core/App && cd src/components/core/App

Then, create a component file inside App:

touch index.jsx

To create a component, add the content below to index.jsx file:

// Module dependencies
import React from 'react';

// Component
const App = () => <div>App component</div>;

// Module exports
export default App;

After that, import App component to the project starting point. To do that, open src/index.jsx file and update with the following changes:

  // Module dependencies
  import ReactDOM from 'react-dom';
  import React from 'react';

+ // Starting point component
+ import App from './components/core/App';

  ...
  ...

  // Render React element into the DOM
- ReactDOM.render(<h1>Welcome to React</h1>, root);
+ ReactDOM.render(<App />, root);

commit: Create App component

Adding the dependency entry point

On the command line, run the command below:

touch src/dependencies/index.js

Then, add the following import statement to src/index.jsx file:

  ...
  import registerServiceWorker from 'registerServiceWorker';
+ import 'dependencies';

commit: Add dependency entry point (1), (2)

Specifying root HTML element

On the command line, create html.js file inside elements folder:

mkdir src/constants/elements && touch src/constants/elements/html.js

Inside html.js file, just export an empty object:

export default {};

commit: Add JavaScript constants for specifying HTML elements

Then, update html.js file with the following changes:

- export default {};
+ export default {
+   root: 'root'
+ };

Open src/index.jsx file and replace static 'html' with JavaScript constant:

+ // Constants
+ import HTML from './constants/elements/html';

  // Render React element into the DOM
- ReactDOM.render(<App />, document.getElementById('html'));
+ ReactDOM.render(<App />, document.getElementById(HTML.root));

commit: Specify root HTML element to the project starting point

Adding an error handler

Open src/index.jsx file and update with the following changes:

  ...
  ...

+ // Initialize root HTML element
+ const root = document.getElementById(HTML.root);
+
+ // Validate root element
+ if (root == null) {
+   throw new Error('No root element found');
+ }
+
  // Render React element into the DOM
- ReactDOM.render(<App />, document.getElementById(HTML.root));
+ ReactDOM.render(<App />, root);

  // Service worker
  registerServiceWorker();

commit: Add error handler

Defining URLs variable

In the project’s root directory, create a variable file inside src/constants/router:

mkdir src/constants/router && touch src/constants/router/urls.js

Then, add the remote repository URL to repo property:

export default {
  repo: 'rxseven/setup-react-app'
};

commit: Add URLs variable (optional)

Adding the permanent environment variables

Create env files in the project’s root directory by running a single command:

touch .env .env.development .env.production

commit: Add permanent environment variables

Specify app URLs

Open .env.development file and add the content below:

# Web application
REACT_APP_WEB_URL=http://localhost:3000

commit: Specify app URL in development environment

Open .env.production file and add the content below:

# Web application
REACT_APP_WEB_URL=https://setup-react-app.herokuapp.com

commit: Specify app URL in production environment

Running scripts across platforms

To be able to run scripts that set and use environment variables across platforms, you may need a cross-env library.

With cross-env, you can have a single command without worrying about setting or using the environment variable properly for the platform. Just set it like you would if it’s running on a POSIX system, and it will take care of setting it properly.

Let’s install it as a development dependency:

yarn add --dev cross-env

commit: Install cross-env package

Installing utility libraries

You might come to the point to choose a JavaScript utility library that gives you more complex functionalities. You might even want to be more flexible when chaining these utility functions or even compose them dynamically into each other. That’s the point in time where you would introduce a utility library.

My recommendations are three libraries, we will be using the following widespread JavaScript utility libraries throughout this project and all of them are required to be installed beforehand.

Lodash - A modern JavaScript utility library delivering modularity, performance & extras.

yarn add lodash

commit: Install lodash package

Ramda - A practical functional library for JavaScript programmers.

When you move towards functional programming in JavaScript, there is no way around this utility library. Even though Lodash comes with its own functional programming derivate (which is Lodash FP), I would recommend using Ramda when dealing with functional programming.

yarn add ramda

commit: Install ramda package

Classnames - A simple JavaScript utility for conditionally joining CSS class names together.

yarn add classnames

commit: Install classnames package

Back to top

Adding Pre-commit Hooks

In this section we’ll look at Git hooks. These hooks are a feature of Git which furthers its extensibility by allowing developers to create event triggered scripts. For more information, see Git Hooks.

What is a pre-commit

The pre-commit hook is the ideal opportunity to run many of the checks that your CI server would run. This hook can be used to make sure certain checks pass before a commit can be considered worthy to be made to the remote repository.

Introducing Husky and Lint-staged

Husky is a really cool library that provides the ability to easily create Git commit hooks allowing you to run arbitrary scripts as a pre-commit sequence.

Lint-staged on the other hand allows you to run arbitrary scripts against currently staged files. In Git, a file is being “staged” after you have “added” it to a commit. This allows you to slightly pick the files that will go into your next commit.

Why we don’t just use Husky to run scripts? the key concept is that we only want to run scripts against the list of files currently staged - nothing else.

Installation

On the command line, let’s install Husky as a development dependency:

yarn add --dev husky

commit: Install husky package

Follow by Lint-staged:

yarn add --dev lint-staged

commit: Install lint-staged package

Configuration

Lint-staged

In the project’s root directory, let’s create a configuration file:

touch .lintstagedrc

Once the file has created, add the placeholder object below:

{}

commit: Create Lint-staged configuration

You may need to define .lintstagedrc as a JSON format to Visual Studio Code’s file association list. In .vscode/settings.json file, add the following line to "files.associations" section:

  {
    // File associations to languages
    "files.associations": {
-     ".babelrc": "json"
+     ".babelrc": "json",
+     ".lintstagedrc": "json"
    }
  }

commit: Add Lint-staged configuration to VSCode's file association list

Husky

Open package.json file and add precommit script to scripts section:

  {
    "build": "node scripts/build.js",
+   "precommit": "lint-staged",
    "start": "node scripts/start.js",
    "test": "node scripts/test.js --env=jsdom"
  }

commit: Add Git pre-commit script

Back to top

Adding React Hot Loader

React Hot Loader is a plugin that allows React components to be live reloaded without the loss of state. It works with Create React App that support both Hot Module Replacement (HMR) and Babel plugins.

Installation

You can safely install it as a regular dependency instead of a development dependency:

yarn add react-hot-loader

commit: Install react-hot-loader package

Configuration

In config/webpack.config.dev.js file, add plugins: ['react-hot-loader/babel'] to Babel loader configuration. Your configuration should look like this:

  // Process JS with Babel.
  {
    test: /\.(js|jsx|mjs)$/,
    ...
    options: {
      ...
      cacheDirectory: true,
+
+     // React Hot Loader
+     plugins: ['react-hot-loader/babel']
    }
  }

Then, add the lines below to src/components/core/App/index.jxs file:

  // Module dependencies
  import React, { Fragment } from 'react';
+ import { hot } from 'react-hot-loader';

  // Component
  const App = () => <div>App component</div>;

  // Module exports
- export default App;
+ export default hot(module)(App);

commit: Setup React Hot Loader

Back to top

Running JavaScript Linting

Code linting is a form of static analysis that will find both code errors as well as highlight syntax not adhering to formatting styles. This means it can help us find problematic code before we commit to version control and more importantly before the code finds its way onto production.

Setup

Installation

Create React App ships with ESLint out of the box! there is no need to install any additional library separately.

Here is a list of ESLint dependencies we got when create a new app with Create React App:

Core

eslint

Shareable configuration

eslint-config-react-app

Plugins

eslint-plugin-flowtype
eslint-plugin-import
eslint-plugin-jsx-a11y
eslint-plugin-react

Configuration

In the project’s root directory, create a configuration file:

touch .eslintrc

commit: Create ESLint configuration

Then, add the following configuration to the newly created file:

{
  "env": {
    "browser": true,
    "node": true
  },
  "parser": "babel-eslint"
}

Open package.json file, cut eslintConfig property and its values:

  {
-   "eslintConfig": {
-     "extends": "react-app"
-   }
  }

In .eslintrc file, paste code snippets from a clipboard and remove eslintConfig key:

  {
    "env": {...},
+   "extends": [
+     "react-app"
+    ],
    "parser": "babel-eslint"
  }

commit: Setup ESLint

You may need to define .eslintrc as a JSON format to Visual Studio Code’s file association list. In .vscode/settings.json file, add the following line to "files.associations" section:

  {
    // File associations to languages
    "files.associations": {
      ".babelrc": "json",
+     ".eslintrc": "json",
      ".lintstagedrc": "json"
    }
  }

commit: Add ESLint configuration to VSCode's file association list

Ignoring files

ESLint supports excluding files from the linting process when ESLint operates on a directory.

In the project’s root directory, let’s create an ignoring file:

touch .eslintignore

commit: Create ESLint ignoring file

To exclude files from linting, add entries to a .eslintignore file:

/build
/config
/scripts
/src/playground
/src/registerServiceWorker.js

commit: Update ignoring list to exclude files from being linted by ESLint

Using a shareable configuration

Create React App ships with its shareable ESLint configuration called eslint-config-react-app and it is awesome! But we be using another popular one from Airbnb which is eslint-config-airbnb.

Installation

On the command line, install the configuration as a development dependency:

yarn add --dev eslint-config-airbnb

commit: Install eslint-config-airbnb package

To list its peer dependencies and their versions, run the command below:

npm info "eslint-config-airbnb@latest" peerDependencies

Then, the output should look like this:

{
  eslint: '^4.9.0',
  'eslint-plugin-import': '^2.7.0',
  'eslint-plugin-jsx-a11y': '^6.0.2',
  'eslint-plugin-react': '^7.4.0'
}

Since Create React App ships with all dependencies listed above (they are eslint-config-react-app’s peer dependencies), we will need to check only the minumum versions of dependencies it requires.

If some are needed to be updated, run yarn remove <dependency> follow by yarn add --dev <dependency>@<version> for each listed items.

You may need to update eslint-plugin-jsx-a11y package, let’s remove the old version first:

yarn remove eslint-plugin-jsx-a11y

Then, install the latest version as a development dependency:

yarn add --dev eslint-plugin-jsx-a11y

commit: Update eslint-plugin-jsx-a11y package to v6.0.3

Configuration

In .eslintrc file, add airbnb to extends section:

  {
    "env": {...},
-   "extends": ["react-app"],
+   "extends": ["react-app", "airbnb"],
    "parser": "babel-eslint"
  }

Note: there is no need to add all peer dependencies to plugins section in .eslintrc file since they are already pre-configured in the shareable configuration.

commit: Setup shareable Airbnb configuration for ESLint

Overriding linting rules

In .eslintrc file, add the following rules section:

{
  "rules": {
    "comma-dangle": ["error", "never"],
    "no-unused-vars": [
      "error",
      {
        "vars": "local",
        "args": "none"
      }
    ],
    "no-useless-escape": "off",
    "import/no-extraneous-dependencies": ["error", { "devDependencies": true }],
    "import/no-named-as-default": "off",
    "import/no-named-as-default-member": "off",
    "jsx-a11y/anchor-is-valid": [
      "error",
      {
        "components": ["Link"],
        "specialLink": ["to"]
      }
    ],
    "jsx-a11y/href-no-hash": "off",
    "jsx-a11y/label-has-for": "off",
    "react/jsx-filename-extension": "off",
    "react/no-string-refs": "off",
    "react/prop-types": "off"
  }
}

commit: Override ESLint shareable rules

Displaying lint output in the editor

We will be using ESLint extension for Visual Studio Code to automatically lint our JavaScript code in the editor.

Installatation

  1. Open Command Palette in Visial Studio Code by pressing command + p.
  2. Type ext install vscode-eslint and hit enter.
  3. Reload Visual Studio Code.

Usage

Head to the extension’s documentation page and follow the instructions.

Running code linting through the CLI

We will define scripts for running code linting against the entire project from the command line.

Configuration

Open package.json file and add the following scripts to scripts section:

  {
    "scripts": {
      "build": "node scripts/build.js",
+     "lint:script": "eslint src/**/*.{js,jsx}",
+     "lint:script:fix": "eslint --fix src/**/*.{js,jsx}",
      "precommit": "lint-staged",
      ...
    }
  }

commit: Add scripts for running code linting through the CLI

Usage

Run the following script to lint JavaScript code with ESLint through the CLI:

yarn lint:script

To automatically fix problems, run the following script instead:

yarn lint:script:fix

With :fix option, it instructs ESLint to try to fix as many issues as possible. The fixes are made to the actual files themselves and only the remaining unfixed issues are output. For more information, see ESLint - Command Line Interface.

Preventing linting violations from being committed

To prevent invalid code from being committed to a repository, we need to setup a pre-commit hook to run ESLint against staged files that about to be committed.

This pre-commit task will check (lint) JavaScript files that are being marked as "staged" via git add before committing valid code to a repository.

In .lintstagedrc file, add the following code block:

  {
+   "src/**/*.{js,jsx}": [
+     "yarn lint:script:fix",
+     "git add"
+   ]
  }

Note: make sure you have yarn lint:script:fix script defined beforehand.

commit: Add pre-commit hook for running code linting against staged files

Back to top

Running Stylesheet Linting

Stylelint is a modern CSS linter that helps you enforce consistent conventions and avoid errors in your stylesheets. It analyzes code and reports errors when a piece of code doesn’t pass the rules defined in the linter’s configuration.

Setup

Installation

Stylelint should be installed as a development dependency:

yarn add --dev stylelint

commit: Install stylelint package

Stylelint by itself supports SCSS syntax very well (as well as other syntaxes) but it was developed to focus on standard CSS.

With stylelint-scss, it introduces rules specific to SCSS syntax. It offers a collection of lint rules for conventions and syntax specific to SCSS.

Let’s install it as well:

yarn add --dev stylelint-scss

commit: Install stylelint-scss package

Configuration

After you have Stylelint installed, you need to create a configuration file for it. This is where you will configure all the lint rules you want Stylelint to check for.

In the project’s root directory, create a configuration file:

touch .stylelintrc

Once the file has created, add the placeholder object below:

{}

commit: Create Stylelint configuration

You may need to define .stylelintrc as a JSON format to Visual Studio Code’s file association list. In .vscode/settings.json file, add the following line to "files.associations" section:

  {
    // File associations to languages
    "files.associations": {
      ".babelrc": "json",
      ".eslintrc": "json"
-     ".lintstagedrc": "json"
+     ".lintstagedrc": "json",
+     ".stylelintrc": "json"
    }
  }

commit: Add Stylint configuration to VSCode's file association list

Using a shareable configuration

We will be using a famous shareable recommended SCSS configuration for Stylelint. We can use it as is or as a foundation for our own configuration.

Installation

On the command line, install the configuration as a development dependency:

yarn add --dev stylelint-config-recommended-scss

commit: Install stylelint-config-recommended-scss package

You may also need another shareable configuration to tweak Stylelint rules to accept CSS modules specific syntax:

yarn add --dev stylelint-config-css-modules

commit: Install stylelint-config-css-modules package

Configuration

In .stylelintrc file, add stylelint-config-recommended-scss to extends section:

- {}
+ {
+   "extends": [
+     "stylelint-config-recommended-scss"
+   ]
+ }

commit: Setup shareable recommended SCSS configuration for Stylelint

Follow by stylelint-config-css-modules:

  {
    "extends": [
-     "stylelint-config-recommended-scss"
+     "stylelint-config-recommended-scss",
+     "stylelint-config-css-modules"
    ]
  }

commit: Setup shareable CSS modules configuration for Stylelint

Overriding linting rules

In .stylelinerc file, add the following rules section to prevent Stylelint from warning against Sass directives:

{
  "rules": {
    "at-rule-no-unknown": [true, {
      "ignoreAtRules": ["each", "extend", "function", "if", "include", "mixin"]
    }],
    "no-descending-specificity": null
  }
}

commit: Override Stylelint shareable rules

Display lint output in the editor

We will be using Stylelint extension for Visual Studio Code to automatically lint our stylesheet code in the editor.

Installatation

  1. Open Command Palette in Visial Studio Code by pressing command + p.
  2. Type ext install stylelint and hit enter.
  3. Reload Visual Studio Code.

Usage

Head to the extension’s documentation page and follow the instructions.

Configuration

To prevent both the editor’s stylesheet built-in linters and this extension from reporting essentially the same errors, we need to disable the editor’s tools in Visual Studio Code’s Workspace Settings.

Let’s Add the following configuration to .vscode/settings.json file:

{
  // Stylelint
  // Prevent both the editor’s stylesheet built-in linters and Stylelint
  // from reporting essentially the same errors.
  "css.validate": false,
  "less.validate": false,
  "scss.validate": false,
}

commit: Setup Stylelint extension for VSCode

Running stylesheet linting through the CLI

We will define a script for running stylesheet linting against the entire project from the command line.

Configuration

Open package.json file and add the following script to scripts section:

  {
    "scripts": {
      ...
      "lint:script:fix": "eslint --fix src/**/*.{js,jsx}",
+     "lint:stylesheet": "stylelint \"src/**/*.scss\"",
      "precommit": "lint-staged",
      ...
    }
  }

Note: be sure to include the quotation marks around file globs. This ensures that you can use the powers of node-glob (like the ** globstar) regardless of your shell. For more information, see The stylelint CLI.

commit: Add script for running stylesheet linting through the CLI

Usage

Run the following script to lint stylesheet with Stylelint through the CLI:

yarn lint:stylesheet

Preventing linting violations from being committed

To prevent invalid stylesheet from being committed to a repository, we need to setup a pre-commit hook to run Stylelint against staged stylesheet files that about to be committed.

This pre-commit task will check (lint) .scss files that are being marked as "staged" via git add before committing valid stylesheet to a repository.

In .lintstagedrc file, add the following code block:

  {
-   "src/**/*.{js,jsx}": [...]
+   "src/**/*.{js,jsx}": [...],
+   "src/**/*.scss": [
+     "yarn lint:stylesheet",
+     "git add"
+   ]
  }

Note: make sure you have yarn lint:stylesheet script defined beforehand.

commit: Add pre-commit hook for running stylesheet linting against staged files

Back to top

Running Static Type Checking

JavaScript is awesome! however, its lack of static typing can be a real pain for developers, bugs only appear at runtime, are hard to find and code refactoring is a real challenge.

This is where static type checkers such as TypeScript and Flow come into play with their main features being:

  • Helping to catch errors early, close to the root cause and at buildtime.
  • Improving code readability and maintainability.

Flow is a static type checker for your JavaScript code that helps you write code with fewer bugs. It does a lot of work to make you more productive. Making you code faster, smarter, more confidently, and to a bigger scale.

Setup

Installation

Recent versions of Flow work with Create React App projects out of the box. All we need to do is install Flow and create a configuration file.

We need to install Flow binary as a development dependency:

yarn add --dev flow-bin

Note: there is no need to install babel-preset-flow since Create React App already supports Flow by default.

commit: Install flow-bin package

Configuration

First, we need to prepare a project for Flow by running the following command to initialize Flow configuration:

yarn flow init

The command will then create .flowconfig file inside the project’s root directory with default options as follows:

[ignore]

[include]

[libs]

[lints]

[options]

[strict]

Next, we need to create a script for running Flow within our project, open package.json file and add the following script to scripts section:

  {
    "scripts": {
      ...
-     "test": "node scripts/test.js --env=jsdom"
+     "test": "node scripts/test.js --env=jsdom",
+     "type": "flow"
    }
  }

commit: Setup Flow

Ignoring files

Flow needs to know which files to read and watch for changes. This set of files is determined by taking all included files and excluding all the ignored files.

The [ignore] section in a .flowconfig file tells Flow to ignore files matching the specified regular expressions when type checking your code. By default, nothing is ignored.

You may need to configure Flow to ignore certain files especially files in node_modules:

  [ignore]
+ .*/node_modules/.*

commit: Update ignoring list to exclude files from being checked by Flow

Specifying file extensions

By default, Flow will look for files with the extensions .js, .jsx, .mjs and .json. You may need to override this behavior with this option.

In .flowconfig file, add a list of file extensions to [options] section as follows:

  [options]
+ module.file_ext=.css
+ module.file_ext=.js
+ module.file_ext=.json
+ module.file_ext=.jsx
+ module.file_ext=.mjs
+ module.file_ext=.scss

commit: Override Flow's file extension list

Library Definitions

When you start a JavaScript project (including React obviously) and use Flow for static type checking, you likely want to use some third-party libraries that were not written with Flow and they are absolutely not under the control of the project.

That means a project using Flow for static type checking may need to reference outside code that either doesn’t have type information or doesn’t have accurate and/or precise type information. By default, Flow will just ignore these libraries leaving them untyped.

In order to handle this, Flow supports the concept of a “library definition” or “libdef” which allow you to describe the interface of a module or library separate from the implementation of that module/library. For more information on library definitions, see this documentation.

Introducing Flow-typed

Flow-typed is a repository of third-party library interface definitions for use with Flow. It is a collection of high-quality library definitions, tests to ensure that definitions remain high quality, and tooling to make it as easy as possible to import them into your project.

Installation

Let’s install the CLI tool on the command line as a development dependency:

yarn add --dev flow-typed

commit: Install flow-typed package

Configuration

We will need to define script for running flow-typed install on the command line. To do this, open package.json file and add the following script to scripts section:

  {
    "scripts": {
      ...
-     "type": "flow"
+     "type": "flow",
+     "type:install": "flow-typed install"
    }
  }

commit: Add script for installing library definitions

Usage

All you have to do when you add one or more new dependencies to your project is run the script on the command line. This will search the libdef repository and download all the libdefs that are relevant for your project and install them for you.

yarn type:install

commit: Update library definitions

Using Flow extension for code editor

We will be using Flow for Visual Studio Code to automatically check our JavaScript in the editor.

Installatation

  1. Open Command Palette in Visial Studio Code by pressing command + p.
  2. Type ext install flowtype.flow-for-vscode and hit enter.
  3. Reload Visual Studio Code.

Configuration

We will be using Flow for static type checking and ESLint for JavaScript linting rather than the editor’s built-in JavaScript & TypeScript validators.

To disable the editor’s built-in tools, we need to add the following configuration to .vscode/settings.json file:

  {
    // Disable built-in code formatter and validator
    "editor.formatOnSave": false,
    "javascript.format.enable": false,
-   "javascript.validate.enable": false,

    ...
    ...

+   // Flow and ESLint
+   // Use Flow for static type checking and ESLint for JavaScript linting
+   // rather than built-in JavaScript & TypeScript validation.
+   "javascript.validate.enable": false
  }

We also need to configure Visual Studio Code to run Flow binary from the project’s dependencies, which is from node_modules:

  {
    ...
    ...

    // File associations to languages
-   "files.associations": {...}
+   "files.associations": {...},
+
+   // Flow
+   // Run Flow from local Node modules.
+   "flow.useNPMPackagedFlow": true

    ...
    ...
  }

commit: Setup Flow extension for VSCode

Usage

Head to the extension’s documentation page and follow the instructions.

Linting

There is no need to install eslint-plugin-flowtype plugin separately, Flow type linting rules for ESLint work with Create React App projects out of the box!

Note: ESLint works with babel-eslint@8.1.1 and higher, please do double check your current version in package.json file. For more information, see this issue.

In case you need to update a new version of babel-eslint library, you need to remove the current version from the project’s development dependency before updating to a new version:

yarn remove babel-eslint

Then, install a new version:

yarn add --dev babel-eslint@8.2.3

commit: Update babel-eslint package to v8.2.3

Running type checking through the CLI

We will define a script for running type checking through the entire project from the command line.

Configuration

Open package.json file and add the following script to scripts section:

  {
    "scripts": {
      ...
      "type": "flow",
+     "type:check": "flow check",
      "type:install: "flow-typed install"
    }
  }

commit: Add script for running type checking through the CLI

Usage

Run the command below to run a full check and print the results through the CLI:

yarn type:check

Preventing type violations from being committed

To prevent invalid code from being committed to a repository, we need to setup a pre-commit hook to run type checking against staged files that about to be committed.

This pre-commit task will run type checking JavaScript files that are being marked as "staged" via git add before committing valid code to a repository.

First, open package.json file and add the following script to scripts section:

  {
    "scripts": {
      ...
      "type:check": "flow check",
+     "type:check:focus": "flow focus-check",
      "type:install: "flow-typed install"
    }
  }

Then, in .lintstagedrc file, add the following line:

  {
    "src/**/*.{js,jsx}": [
      "yarn lint:script:fix",
+     "yarn type:check:focus",
      "git add"
    ]
  }

commit: Add pre-commit hook for running type checking against staged files

Applying type checking

You may need to apply type checking to the project starting point and App component.

Project startin point

Open src/index.jsx file and add the content below:

+ // @flow
  ...
  ...

commit: Add type checking to the project starting point

App component

Open src/components/core/App/index.jsx file and add the content below:

+ // @flow
  // Module dependencies
- import React from 'react';
+ import * as React from 'react';
  import { hot } from 'react-hot-loader';

+ // Types
+ type Return = React.Node;
+
  // Component
- const App = () => <div>App component</div>;
+ const App = (): Return => <div>App component</div>;

  // Module exports
  export default hot(module)(App);

commit: Add type checking to App component

Back to top

Running Tests

Writing tests is an essential part of software development to ensure a robust application. Tests enable you to automatically verify that your application is working on a certain level. The certain level depends on the quality, quantity (coverage) and type of your tests (unit tests, integration tests, end-to-end tests).

This section will guide you through the steps necessary to setup testing tools in Create React App from the ground up.

Configuring Jest

Jest is a delightful JavaScript test framework running on Node.js. It is the official testing library by Facebook and it is used by Facebook to test all JavaScript code including React applications. Jest introduced the so called snapshot tests which can be used perfectly as supplement to your React unit and integration tests with Enzyme.

Installation

Jest works out of the box with Create React App, there is no need to install any additional library separately. But there are some small changes we need to tweak to suit our preferences.

Configuration

We will define Jest configuration in jest.config.json file rather than in package.json file (default).

On the command line, create a configuration file in the project’s root directory:

touch jest.config.json

Open package.json file, cut jest property and its values:

  {
-   "jest": {...}
  }

Sort those configuration properties alphabetically, remove jest key, and paste it in jest.config.json file:

{
  "collectCoverageFrom": ["src/**/*.{js,jsx,mjs}"],
  "moduleFileExtensions": [
    "web.js",
    "js",
    "json",
    "web.jsx",
    "jsx",
    "node",
    "mjs"
  ],
  "moduleNameMapper": {
    "^react-native$": "react-native-web"
  },
  "setupFiles": ["<rootDir>/config/polyfills.js"],
  "testEnvironment": "node",
  "testMatch": [
    "<rootDir>/src/**/__tests__/**/*.{js,jsx,mjs}",
    "<rootDir>/src/**/?(*.)(spec|test).{js,jsx,mjs}"
  ],
  "testURL": "http://localhost",
  "transform": {
    "^.+\\.(js|jsx|mjs)$": "<rootDir>/node_modules/babel-jest",
    "^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
    "^(?!.*\\.(js|jsx|mjs|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
  },
  "transformIgnorePatterns": ["[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs)$"]
}

In order to tell Jest to use your configuration file for the test environment, in package.json file update the following test script with:

- "test": "node scripts/test.js --env=jsdom"
+ "test": "node scripts/test.js --env=jsdom --config jest.config.json"

commit: Setup Jest

Ignoring paths

coveragePathIgnorePatterns property is an array of regexp pattern strings that are matched against all file paths before executing the test. If the file path matches any of the patterns, coverage information will be skipped. For more information on coverage path ignoring patterns, see Configuring Jest.

We will need to configure Jest to ignore the following paths when running code coverage:

  {
    "collectCoverageFrom": ["src/**/*.{js,jsx,mjs}"],
+   "coveragePathIgnorePatterns": [
+     "<rootDir>/build/",
+     "<rootDir>/config/",
+     "<rootDir>/node_modules/",
+     "<rootDir>/src/config/",
+     "<rootDir>/src/constants/",
+     "<rootDir>/src/dependencies/",
+     "<rootDir>/src/index.jsx",
+     "<rootDir>/src/registerServiceWorker.js"
+   ],
    ...
    ...
  }

commit: Add code coverage ignoring paths

Configuring Enzyme

Enzyme is a JavaScript testing utility for React that makes it easier to assert, manipulate, and traverse your React Component’s output. Its API is meant to be intuitive and flexible by mimicking jQuery’s API for DOM manipulation and traversal.

Enzyme uses the React Test Utilities underneath, but is more convenient, readable, and powerful.

Installation

We will need to install Enzyme along with an adapter corresponding to the version of React we are using. For instance, if you are using Enzyme with React 16, you can run:

yarn add --dev enzyme

commit: Install enzyme package

and:

yarn add --dev enzyme-adapter-react-16

commit: Install enzyme-adapter-react-16 package

Each adapter may have additional peer dependencies which we will need to install as well. For instance, enzyme-adapter-react-16 has peer dependencies on react, react-dom, and react-test-renderer.

We just need to install the missing one:

yarn add --dev react-test-renderer

commit: Install react-test-renderer package

Additionally, we find jest-enzyme helpful to simplify our tests with readable matchers. So, let’s also install it as a development dependency:

yarn add --dev jest-enzyme

commit: Install jest-enzyme package

Configuration

The adapter will also need to be configured in the global setup file. To create a configuration file, run the following command in the project’s root directory:

touch src/tests/setup.js

Then, add the configuration below:

// Module dependencies
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import 'jest-enzyme';

// Configure Enzyme adapter
configure({ adapter: new Adapter() });

Since we bootstraped the project with Create React App and we ran yarn eject before creating src/tests/setup.js file, the resulting package.json file won’t contain any reference to it, so we should manually create the property setupTestFrameworkScriptFile in the configuration for Jest.

To do that, open jest.config.json file and add the line below:

  {
    ...
    "setupFiles": ["<rootDir>/config/polyfills.js"],
+   "setupTestFrameworkScriptFile": "<rootDir>/src/tests/setup.js",
    "testEnvironment": "node",
    ...
  }

Then, we have to update the reference path in config/paths.js file:

  module.exports = {
    ...
    yarnLockFile: resolveApp('yarn.lock'),
-   testsSetup: resolveApp('src/setupTests.js'),
+   testsSetup: resolveApp('src/tests/setup.js'),
    appNodeModules: resolveApp('node_modules'),
    ...
  };

commit: Setup Enzyme

Configuring Redux testing

In order to be able to run tests on Redux application, you will need install a redux-mock-store library to create a mock store for your Redux testing.

On the command line, let’s install the library as a development dependency:

yarn add --dev redux-mock-store

commit: Install redux-mock-store package

Linting

To make ESLint happy when writing tests with Jest, we need to install an ESLint plugin for Jest.

Installation

Let’s install eslint-plugin-jest as a development dependency:

yarn add --dev eslint-plugin-jest

commit: Install eslint-plugin-jest package

Configuration

Open .eslintrc file and add jest to the plugins section:

  {
    ...
    "parser": "babel-eslint",
+   "plugins": ["jest"],
    "rules": {...}
    ...
  }

Then, configure the rules for the plugin within rules section as follows:

  {
    "rules": {
      ...
      "import/no-named-as-default-member": "off",
+     "jest/no-disabled-tests": "warn",
+     "jest/no-focused-tests": "error",
+     "jest/no-identical-title": "error",
+     "jest/prefer-to-have-length": "warn",
+     "jest/valid-expect": "error",
      "jsx-a11y/anchor-is-valid": [...],
      ...
    }
  }

We will also need to whitelist the environment variables provided by Jest by doing:

  {
    "env": {
      "browser": true,
-     "node": true
+     "node": true,
+     "jest/globals": true
    },
    ...
  }

This plugin exports a recommended configuration that enforces good testing practices. To enable this configuration, update .eslintrc file as below:

  {
    "env": {...},
-   "extends": ["react-app", "airbnb"],
+   "extends": ["react-app", "airbnb", "plugin:jest/recommended"],
    "parser": "babel-eslint",
    ...
  }

commit: Setup ESLint plugin for Jest

Using Jest extension for code editor

We will be using Jest for Visual Studio Code to automatically test our JavaScript in the editor.

Installatation

  1. Open Command Palette in Visial Studio Code by pressing command + p.
  2. Type ext install orta.vscode-jest and hit enter.
  3. Reload Visual Studio Code.

Usage

Head to the extension’s documentation page and follow the instructions.

Running tests through the CLI

We will define a scripts for running tests through the entire project from the command line.

Configuration

test script is already set and ready to use.

Jest has an integrated coverage reporter that requires no configuration, but we will need to add this to our workflow manually.

Let’s open package.json file and add the following script to scripts section:

  {
    "scripts": {
      ...
      "test": "node scripts/test.js --env=jsdom --config jest.config.json",
+     "test:coverage": "npm test -- --coverage --no-cache",
      "type": "flow",
      ...
    }
  }

Note: the cache should only be disabled if you are experiencing caching related problems. On average, disabling the cache makes Jest at least two times slower. For more information on caching, see Jest CLI Options and Caching Issues.

commit: Add script for running tests with coverage report through the CLI

Usage

Run the following script to run tests with Jest through the CLI:

yarn test

By default, when you run the command above, Jest will only run the tests related to files changed (modified) since the last commit. This is an optimization designed to make your tests run fast regardless of how many tests in the project you have. However, you can also press a in the watch mode to force Jest to run all tests.

Note: Jest will always run all tests on a continuous-integration server.

Moreover, you can use the following command to run tests include a coverage report:

yarn test:coverage

Note: tests run much slower with coverage so it is recommended to run it separately from your normal workflow.

Preventing broken code from being committed

To prevent broken code from being committed to a repository, we need to setup a pre-commit hook to run tests against staged files that about to be committed.

This pre-commit task will run tests against JavaScript files that are being marked as "staged" via git add before committing working code to a repository.

First, open package.json file and add the following script to scripts section:

  {
    "scripts": {
      ...
      "test:coverage": "npm test -- --coverage --no-cache",
+     "test:staged": "cross-env CI=true node scripts/test.js --env=jsdom --config jest.config.json --findRelatedTests"
    }
  }

Note: when set --findRelatedTests, will find and run the tests that cover a space separated list of source files that were passed in as arguments. Useful for pre-commit hook integration to run the minimal amount of tests necessary. For more information, see Jest CLI Options.

Note: when set CI=true, Create React App makes the test runner non-watching. Most CIs set this flag by default. Details are available here.

Then, in .lintstagedrc file, add the following line:

  {
    "src/**/*.{js,jsx}": [
+     "yarn test:staged",
      "yarn lint:script:fix",
      "git add"
    ]
  }

commit: Add pre-commit hook for running tests against staged files

Applying tests

You may need to apply tests to App component. On the command line, let’s create a test file:

touch src/components/core/App/index.test.js

Then, add the tests below:

// Module dependencies
import { shallow } from 'enzyme';
import React from 'react';

// Components
import App from './index';

// Tests
describe('components/core/App', () => {
  it('should render without crashing', () => {
    shallow(<App />);
  });
});

commit: Add tests to App component

Back to top

Formatting Code Automatically

Having consistent code formatting and style is an important part of reducing cognitive load when working with other developers or when jumping around between projects.

We will be using Prettier, a tool that will format code against some opinionated and standard default formatting rules to ensure all final code maintains a consistent style.

Setup

Installation

Prettier should be installed as a development dependency:

yarn add --dev --exact prettier

commit: Install prettier package

Configuration

In the project’s root directory, create a configuration file:

touch .prettierrc

commit: Create Prettier configuration

Then, add the following configuration to the newly created file:

{
  "singleQuote": true
}

commit: Setup Prettier

You may need to define .prettierrc as a JSON format to Visual Studio Code’s file association list. In .vscode/settings.json file, add the following line to "files.associations" section:

  {
    // File associations to languages
    "files.associations": {
      ...
      ".lintstagedrc": "json",
+     ".prettierrc": "json",
      ".stylelintrc": "json"
    }
  }

commit: Add Prettier configuration to VSCode's file association list

Ignoring code

Prettier offers an escape hatch to ignore a block of code or prevent entire files from being formatted.

In the project’s root directory, let’s create an ignoring file:

touch .prettierignore

commit: Create Prettier ignoring file

To exclude files from formatting, add entries to a .prettierignore file:

/config
/scripts
package.json
README.md

commit: Update ignoring list to exclude files from being formatted by Prettier (1), (2)

Using Prettier extension for code editor

We will be using Prettier formatter for Visual Studio Code to automatically format our code in the editor.

Installatation

  1. Open Command Palette in Visial Studio Code by pressing command + p.
  2. Type ext install prettier-vscode and hit enter.
  3. Reload Visual Studio Code.

Configuration

There is one change required to the editor’s settings is to ensure the Format On Save functionality is triggered. This ensures the Prettier extension applies the format rules on each save of a supported file type.

To do this, we will need to remove the existing configuration we’ve done in the previous section in .vscode/settings.json file:

  {
-   // Disable built-in code formatter and validator
-   "editor.formatOnSave": false,
-   "javascript.format.enable": false,

    ...
    ...
  }

Then, add the configuration below:

  {
    ...
    ...

+   // Prettier
+   // Format a file on save.
+   "editor.formatOnSave": true,

    // File associations to languages
    "files.associations": {..}
  }

Next, we have to disable the default JavaScript formatter (editor’s built-in) and allow Prettier to handle code formatting:

  {
    ...
    ...

    // Flow
    // Run Flow from local Node modules.
    "flow.useNPMPackagedFlow": true,

+   // Prettier
+   // Disable the default JavaScript formatter (editor’s built-in) and allows
+   // Prettier to handle code formatting.
+   "javascript.format.enable": false
+
    ...
    ...
  }

commit: Setup Prettier extension for VSCode

Usage

Head to the extension’s documentation page and follow the instructions.

Fixing formatting conflicts

We need to configure Prettier and ESLint to work together.

Solution

prettier-eslint is a lightweight library that will run your code through Prettier and then run ESLint --fix.

This solution allows you to format your code via Prettier, and then pass the result of that to ESLint --fix. This way you can get the benefits of Prettier’s superior formatting capabilities, but also benefit from the configuration capabilities of ESLint.

For files with an extension of .css, .scss, or .json this only runs Prettier since ESLint cannot process those.

Installation

We need to install this library as a development dependency:

yarn add --dev prettier-eslint

commit: Install prettier-eslint package

Configuration

Add the configuration below to settings.json file:

  {
    ...
    ...

    // Flow and ESLint
    // Use Flow for static type checking and ESLint for JavaScript linting
    // rather than built-in JavaScript & TypeScript validation.
-   "javascript.validate.enable": false
+   "javascript.validate.enable": false,
+
+   // Prettier
+   // Use prettier-eslint instead of prettier. Other settings will only be
+   // fallbacks in case they could not be inferred from ESLint rules.
+   "prettier.eslintIntegration": true
  }

commit: Setup Prettier and ESLint integration

Running code formatting through the CLI

Running Prettier this way on the command line outputs to the screen the format changes that would have been made to your code based on the rules of Prettier and ESLint.

Installation

We need to install a prettier-eslint-cli library, this CLI allows you to use prettier-eslint on one or multiple files. prettier-eslint-cli forwards on the filePath and other relevant options to prettier-eslint which identifies the applicable ESLint configuration for each file and uses that to determine the options for prettier and eslint --fix.

Let’s install the CLI tool as a development dependency:

yarn add --dev prettier-eslint-cli

commit: Install prettier-eslint-cli package

Configuration

Open package.json file and add the following script to scripts section:

  {
    "scripts": {
      "build": "node scripts/build.js",
+     "format": "prettier-eslint --write src/**/*.{js,json,jsx,scss}",
      "lint:script": "eslint src/**/*.{js,jsx}",
      ...
    }
  }

commit: Add script for running code formatting through the CLI

Usage

Run the following script to format code with Prettier throught the CLI:

yarn format

Preventing formatting violations from being committed

To prevent invalid code from being committed to a repository, we need to setup a pre-commit hook to run Prettier against staged files that about to be committed.

This pre-commit task will re-formatting files that are being marked as "staged" via git add before committing consistent code formatting and style to a repository.

In .lintstagedrc file, add the following code block:

  {
    ...
-   "src/**/*.scss": [...]
+   "src/**/*.scss": [...],
+   "src/**/*.{js,json,jsx,scss}": [
+     "yarn format",
+     "git add"
+   ]
  }

Note: make sure you have yarn format script defined beforehand.

commit: Add pre-commit hook for running code formatting against staged files

Back to top

Using JSX Control Statements

There is no built-in looping or conditional syntax in React. JSX is not a templating library, it’s declarative syntactic sugar over functional JavaScript expressions. For more information, see React - Conditional Rendering.

Setup

JSX Control Statements is a Babel plugin that extends JSX to add basic control statements (conditionals and loops). It provides a component-like syntax that keeps your render functions neat and readable, but desugars into clean, readable JavaScript.

Installation

On the command line, install the library as a development dependency:

yarn add --dev babel-plugin-jsx-control-statements

commit: Install babel-plugin-jsx-control-statements package

Configuration

Open .babelrc file and add the following line:

  {
+   "plugins": ["jsx-control-statements"],
    "presets": ["react-app"]
  }

commit: Setup JSX control statements

Linting

Since all control statements are transformed via Babel, no require or import calls are needed. This in turn would lead to warnings or errors by ESLint about undefined variables.

But fortunately you can use this ESLint plugin for JSX Control Statements to lint your code.

Installation

Let’s install the ESLint plugin as a development dependency:

yarn add --dev eslint-plugin-jsx-control-statements

commit: Install eslint-plugin-jsx-control-statements package

Configuration

Open .eslintrc file and add the following configuration:

  {
    "env": {
      "browser": true,
-     "jest/globals": true
+     "jest/globals": true,
+     "jsx-control-statements/jsx-control-statements": true
    },
-   "extends": ["react-app", "airbnb", "plugin:jest/recommended"],
+   "extends": [
+     "react-app",
+     "airbnb",
+     "plugin:jest/recommended",
+     "plugin:jsx-control-statements/recommended"
+   ],
    ...
-   "plugins": ["jest"],
+   "plugins": ["jest", "jsx-control-statements"],
    "rules": {
      ...
+     "jsx-control-statements/jsx-choose-not-empty": "warn",
+     "jsx-control-statements/jsx-for-require-each": "warn",
+     "jsx-control-statements/jsx-for-require-of": "warn",
+     "jsx-control-statements/jsx-if-require-condition": "warn",
+     "jsx-control-statements/jsx-otherwise-once-last": "warn",
+     "jsx-control-statements/jsx-use-if-tag": "warn",
+     "jsx-control-statements/jsx-when-require-condition": "warn",
+     "jsx-control-statements/jsx-jcs-no-undef": "warn",
      ...
+     "react/jsx-no-undef": ["error", { "allowGlobals": true }],
      ...
    }
  }

commit: Update ESLint configuration to support JSX control statements syntax

Back to top

Using Absolute Imports

By default ES6 modules in Create React App environment use relative paths like ../, which is fine for cases where the files you’re importing are relatively close within the file tree (for example, index.jsx and index.test.js).

But using relative paths is a real pain when you start dealing with deeply nested tree structures because you end up with dot.dot syndrome 😩

Implementing absolute imports in Create React App

Create React App supports the NODE_PATH variable for setting up custom import paths. Let’s improve our development workflow with absolute imports by adding the following line to .env file:

# Absolute path
NODE_PATH=src/

This solution works with all environments since variables defined in .env file will act as the defaults.

There is a few more things we need to do...

Linting

To prevent ESLint’s import/no-extraneous-dependencies rule from violation since it doesn’t recognize absolute paths, we need to tell ESLint to aware of this.

To do this, open .eslintrc file and add the following settings next to rules section:

{
  "settings": {
    "import/resolver": {
      "node": {
        "moduleDirectory": ["node_modules", "src/"]
      }
    }
  }
}

Type checking

To prevent Flow’s Cannot resolve module error occure, we also need to tell Flow to aware of this as well.

By default, Flow will look in directories named node_modules for node modules. You can configure this behavior with this option.

In .flowconfig file, add resolve_dirname property to [options] section as below:

  [options]
  ...
  module.file_ext=.scss
+ module.system.node.resolve_dirname=src

  [strict]

Now, you are all good with ES6 absolute imports.

commit: Setup absolute import statements

Applying absolute imports

You may need to apply absolute imports to the project starting point.

To do this, open src/index.jsx file and update with the following changes:

  ...
  ...

- import registerServiceWorker from './registerServiceWorker';
+ import registerServiceWorker from 'registerServiceWorker';

  // Starting point component
- import App from './components/core/App';
+ import App from 'components/core/App';

  // Constants
- import HTML from './constants/elements/html';
+ import HTML from 'constants/elements/html';

commit: Apply absolute imports to the project starting point

Back to top

Using HTTPS in Development

In many circumstances, you may require the development server to serve your web pages over https:// protocol.

With Create React App, set the HTTPS environment variable to true, then start the development server as usual with yarn start:

HTTPS=true yarn start

Note: the development server will use a self-signed certificate, so your web browser will almost definitely display a warning upon accessing the page.

Adding a particular script

Let’s make our development workflow great again by creating a particular script for running the app over HTTPS.

To do this, open package.json file and add the following script to scripts section:

  {
    ...
    "start": "node scripts/start.js",
+   "start:https": "HTTPS=true yarn start",
    ...
  }

commit: Add script for running the app over HTTPS protocol

Usage

Run yarn start:https on the command line and view your app in the browser at https://localhost:3000.

Back to top

Debugging in the Editor

Visual Studio Code supports debugging out of the box with Create React App. This feature enables you to write and debug your React code without leaving the editor, and most importantly it enables you to have a continuous development workflow, where context switching is minimal, as you don’t have to switch between tools.

Installation

You would need to have the latest version of Chrome Debugger Extension installed, follow the installation below:

  1. Open Command Palette in Visial Studio Code by pressing command + p.
  2. Type ext install debugger-for-chrome and hit enter.
  3. Reload Visual Studio Code.

Configuration

Create a configuration file in .vscode folder by running a command in the project’s root directory:

touch .vscode/launch.json

Then, add the content below to launch.json file:

{
  "configurations": [
    {
      "name": "Debug JavaScript",
      "request": "launch",
      "sourceMapPathOverrides": {
        "webpack:///src/*": "${webRoot}/*"
      },
      "type": "chrome",
      "url": "http://localhost:3000",
      "webRoot": "${workspaceRoot}/src"
    }
  ],
  "version": "0.2.0"
}

commit: Setup JavaScript debugging for VSCode

Usage

  1. Start your app by running yarn start or yarn start:https command.
  2. Open Debug panel in Visual Studio Code, select Debug JavaScript from a drop-down menu.
  3. Start debugging by pressing F5 or by clicking the Start Debugging button next to the drop-down menu.
  4. You can then write code as usual, set breakpoints, make changes to the code, and debug your newly modified code, all from your editor.

Back to top

Debugging Tests

There are different ways to setup a debugger for Jest and Enzyme tests in Create React App. We will cover only debugging in Chrome and Visual Studio Code.

Note: debugging tests requires Node version 8 or higher.

Debugging tests in Chrome

Configuration

Add the following script to the scripts section in package.json file:

  {
    "scripts": {
      ...
      "test:coverage": "npm test -- --coverage --no-cache",
+     "test:debug": "react-scripts --inspect-brk test --runInBand --env=jsdom",
      "test:staged": "...",
      ...
    }
  }

commit: Add script for debugging tests in Chrome

Usage

Follow the instruction here.

Debugging tests in Visual Studio Code

Debugging Jest tests is supported out of the box for Visual Studio Code.

Configuration

Open .vscode/launch.json file and add the following configuration next to Debug JavaScript block:

  {
    "configurations": [
      {
        "name": "Debug JavaScript",
        ...
        ...
-     }
+     },
+     {
+       "args": ["test", "--runInBand", "--no-cache", "--env=jsdom"],
+       "console": "integratedTerminal",
+       "cwd": "${workspaceRoot}",
+       "internalConsoleOptions": "neverOpen",
+       "name": "Debug Tests",
+       "request": "launch",
+       "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/react-scripts",
+       "protocol": "inspector",
+       "type": "node"
+     }
    ],
    "version": "0.2.0"
  }

commit: Setup tests debugging for VSCode

Usage

  1. Start your app by running yarn start or yarn start:https command.
  2. Open Debug panel in Visual Studio Code, select Debug Tests from a drop-down menu.
  3. Start debugging by pressing F5 or by clicking the Start Debugging button next to the drop-down menu.
  4. You can then write code as usual, set breakpoints, make changes to the code, and debug your newly modified code, all from your editor.

Back to top

Adding Sass and Post-Processing CSS

CSS pre-processors are in our development life for years. In their first implementations, they had few features. But nowadays, they are the key ingredients and must have tools for CSS development.

This section will walk you through the steps necessary to setup Sass and PostCSS in Create React App from the ground up.

Sass

Installation

First, we need to install a library that provides binding for Node.js to LibSass as a development dependency.:

yarn add --dev node-sass

commit: Install node-sass package

We will also need to install Sass loader for Webpack:

yarn add --dev sass-loader

commit: Install sass-loader package

Configuring Sass in development environment

Open config/webpack.config.dev.js file and add the following Sass configuration next to CSS section (test: /\.css$/):

// Sass
{
  test: /\.scss$/,
  use: [
    {
      loader: require.resolve('style-loader')
    },
    {
      loader: require.resolve('css-loader'),
      options: {
        sourceMap: true,
        importLoaders: 2
      }
    },
    {
      loader: require.resolve('sass-loader')
    }
  ]
},

commit: Setup Sass in development environment

Configuring Sass in production environment

Open config/webpack.config.prod.js file and add the following Sass configuration next to CSS section (test: /\.css$/):

// Sass
{
  test: /\.scss$/,
  use: ExtractTextPlugin.extract(
    Object.assign(
      {
        fallback: {
          loader: require.resolve('style-loader'),
          options: {
            hmr: false,
          },
        },
        use: [
          {
            loader: require.resolve('css-loader'),
            options: {
              minimize: true,
              sourceMap: shouldUseSourceMap,
              importLoaders: 2
            }
          },
          {
            loader: require.resolve('sass-loader')
          }
        ]
      },
      extractTextPluginOptions
    )
  )
},

commit: Setup Sass in production environment

PostCSS

Installation

Create React App ships with PostCSS loader for Webpack out of the box. It includes everything we need to get PostCSS running. We don’t need to install any additional dependencies.

Configuring PostCSS in development environment

Open config/webpack.config.dev.js file, under Sass section, add the following PostCSS configuration between css-loader and sass-loader:

{
  loader: require.resolve('postcss-loader'),
  options: {
    ident: 'postcss',
    plugins: () => [
      require('postcss-flexbugs-fixes'),
      autoprefixer({
        browsers: [
          '>1%',
          'last 4 versions',
          'Firefox ESR',
          'not ie < 9'
        ],
        flexbox: 'no-2009',
      })
    ]
  }
},

commit: Setup PostCSS in development environment

Configuring PostCSS in production environment

Open config/webpack.config.prod.js file, under Sass section, add the following PostCSS configuration between css-loader and sass-loader:

{
  loader: require.resolve('postcss-loader'),
  options: {
    ident: 'postcss',
    plugins: () => [
      require('postcss-flexbugs-fixes'),
      autoprefixer({
        browsers: [
          '>1%',
          'last 4 versions',
          'Firefox ESR',
          'not ie < 9'
        ],
        flexbox: 'no-2009'
      })
    ]
  }
},

commit: Setup PostCSS in production environment

Excluding CSS files from being committed

Since we will be implementing SCSS rather than plain CSS. Therefore, we need to exclude all CSS files from being committed to the repository. To do this, open .gitignore file and add the following entry:

# generated assets
/src/**/*.css

commit: Update Git ignoring list to prevent compiled CSS from being committed to source control

Back to top

Adding CSS Modules

A CSS Module is not an official specification or an implementation in the browser but rather a process in a build step (with the help of module bundlers like Webpack or Browserify) that loads CSS scoped to a particular document. CSS module loader will generate a unique name (i.e. kinda like namespaced) for each CSS class at the time of loading the CSS document (Interoperable CSS to be precise). For more information on learning CSS modules, see this article.

Basic setup

CSS Loader is a Webpack loader that can parse a CSS file and apply various transforms to it. Crucially it has a CSS Modules mode that can take your CSS and hash the classes as mentioned above.

Installation

Lucky us! It turns out the project generated by Create React App includes all the Webpack loaders we need to get CSS Modules running. We don’t need to install any additional dependencies.

Configuring CSS modules in development environment

Open config/webpack.config.dev.js file, under Sass section, update css-loader block with the following changes:

  {
    loader: require.resolve('css-loader'),
    options: {
      sourceMap: true,
-     importLoaders: 2
+     importLoaders: 2,
+     modules: true,
+     localIdentName: '[path]___[name]__[local]___[hash:base64:5]'
    }
  }

commit: Setup CSS modules in development environment

Configuring CSS modules in production environment

Open config/webpack.config.prod.js file, under Sass section, update css-loader block with the following changes:

  {
    loader: require.resolve('css-loader'),
    options: {
      minimize: true,
      sourceMap: shouldUseSourceMap,
-     importLoaders: 2
+     importLoaders: 2,
+     modules: true,
+     localIdentName: '[path]___[name]__[local]___[hash:base64:5]'
    }
  }

commit: Setup CSS modules in production environment

Advanced usage

CSS Modules is a specification that can be implemented in multiple ways.

Webpack CSS Loader itself has several disadvantages:

  • You have to use camelCase CSS class names.
  • You have to use styles object whenever constructing a className.
  • Mixing CSS Modules and global CSS classes is cumbersome.
  • Reference to an undefined CSS Module resolves to undefined without a warning.

Introducing a Babel plugin for React CSS modules

babel-plugin-react-css-modules implements automatic mapping of CSS modules using compile time resolution. Every CSS class is assigned a local-scoped identifier with a global unique name.

styleName attribute is not a React defined attribute but the plugin defined attribute. At compile time, the plugin merges the styleName with the className, allowing you to write elegant code.

The advantages of using Bable plugin are:

  • You are not forced to use the camelCase naming convention.
  • You do not need to refer to the styles object every time you use CSS Modules.
  • There is clear distinction between global CSS and CSS modules.

Installation

Let’s install the plugin as a development dependency:

yarn add --dev babel-plugin-react-css-modules

commit: Install babel-plugin-react-css-modules package

The default configuration should work out of the box with Create React App, but to add support for different CSS syntaxes (e.g. SCSS), we will need to install PostCSS syntax loader for SCSS to transform SCSS source code alongside CSS:

yarn add --dev postcss-scss

commit: Install postcss-scss package

Configuration

Open .babelrc file and add the configuration below:

  {
-   "plugins": ["jsx-control-statements"],
+   "plugins": [
+     "jsx-control-statements",
+     [
+       "react-css-modules",
+       {
+         "filetypes": {
+           ".scss": {
+             "syntax": "postcss-scss"
+           }
+         }
+       }
+     ]
+   ],
    "presets": ["react-app"]
  }

commit: Setup Babel plugin for React CSS modules

Back to top

Adding Redux

Installing Redux ecosystem

Redux

Redux is a very popular library that provides a predictable state container for JavaScript applications.

yarn add redux

commit: Install redux package

React Redux

React Redux is the official bindings between React app and Redux. It adds some useful syntactic sugar for binding your components to Redux state.

yarn add react-redux

commit: Install react-redux package

Redux Saga

Redux Saga is a Redux middleware library, that is designed to make handling side effects (i.e. asynchronous actions like data fetching and impure things like accessing the browser cache) in your Redux app nice and simple. It achieves this by leveraging an ES6 Generators, allowing you to write asynchronous code that looks synchronous, and is very easy to test and handle failures.

yarn add redux-saga

commit: Install redux-saga package

Reselect

Reselect is a tiny memoized selector library that provides a convenient way of getting values from a store in a React & Redux application.

yarn add reselect

commit: Install reselect package

Immutable

Because Redux doesn’t allow us to mutate the application state, it can be helpful to enforce yourself by modeling application state with immutable data structures.

Immutable.js provides many Persistent Immutable data structures with mutative interfaces, and they’re implemented in an efficient way.

yarn add immutable

commit: Install immutable package

Redux Immutable

Redux utility for creating an equivalent function of Redux combineReducers that works with Immutable.js state.

yarn add redux-immutable

commit: Install redux-immutable package

Redux Form

A Higher order component decorator for forms using React and Redux to keep form state in a Redux store.

yarn add redux-form

commit: Install redux-form package

Normalizr

Many APIs return JSON data that has deeply nested objects. Using data in this kind of structure is often very difficult for JavaScript applications, especially those using Redux.

The idea behind Normalizr is to take an API response that has nested objects with a schema definition and flatten them.

yarn add normalizr

commit: Install normalizr package

Library Definitions

Finally, don’t forget to update the library definitions:

yarn type:install

commit: Update library definitions

Creating a folder structure

To create a Redux folder structure, run the following command in the project’s root directory:

cd src && mkdir reducers sagas store components/core/Root

Now, your Redux folder structure should look like this:

src
├── components
│   └── core
│       └── Root
├── reducers
├── sagas
└── store

Creating a root reducer

One day, your app would grow more complex, you’ll want to split your reducing function into separate functions, each managing independent parts of the state (domain).

A root reducer turns an object whose values are different reducing functions into a single reducing function you can pass to a store.

On the command line, run the following command:

touch src/reducers/index.js

Then, add the content below:

// Module dependencies
import { combineReducers } from 'redux-immutable';

// Combine reducers
const reducer = combineReducers({
  data: () => null
});

// Module exports
export default reducer;

We will also need to configure Jest to ignore root reducer when running code coverage, open jest.config.json file and update with the following changes:

  {
    "collectCoverageFrom": ["src/**/*.{js,jsx,mjs}"],
    "coveragePathIgnorePatterns": [
      ...
      "<rootDir>/src/dependencies/",
+     "<rootDir>/src/reducers/",
      "<rootDir>/src/index.jsx",
      ...
    ]
  }

commit: Create root Redux reducer

Creating a root saga

On the command line, run the following command:

touch src/sagas/index.js

Then, add the content below:

// Module dependencies
import { map } from 'ramda';
import { all, fork } from 'redux-saga/effects';

// Combine sagas
const sagas = {};

// Root saga
function* root() {
  yield all(map(fork, sagas));
}

// Module exports
export default root;

We will also need to configure Jest to ignore root saga when running code coverage, open jest.config.json file and update with the following changes:

  {
    "collectCoverageFrom": ["src/**/*.{js,jsx,mjs}"],
    "coveragePathIgnorePatterns": [
      ...
      "<rootDir>/src/reducers/",
+     "<rootDir>/src/sagas/",
      "<rootDir>/src/tests/setup.js",
      ...
    ]
  }

commit: Create root Redux saga

Configuring a store

On the command line, run the following command:

touch src/store/setup.js

Then, add the configuration below:

// Module dependencies
import { Map } from 'immutable';
import { applyMiddleware, compose, createStore } from 'redux';
import createSagaMiddleware from 'redux-saga';

import reducer from 'reducers';
import saga from 'sagas';

// Initialize middleware
const sagaMiddleware = createSagaMiddleware();

// Initialize state
const initialState = Map();

// Store configuration
const configureStore = () => {
  // Create store
  const store = createStore(
    reducer,
    initialState,
    compose(
      // Middleware
      applyMiddleware(sagaMiddleware),

      // Redux DevTools Extension
      window.devToolsExtension ? window.devToolsExtension() : f => f
    )
  );

  // Run middleware
  sagaMiddleware.run(saga);

  // Check if Webpack Hot Module Replacement is enabled
  if (process.env.NODE_ENV !== 'production') {
    if (module.hot) {
      // Enable HMR by accepting update of dependency
      module.hot.accept('../reducers', () => {
        // Replaces the reducer currently used by the store to calculate the state
        store.replaceReducer(reducer);
      });
    }
  }

  // Return store configuration
  return store;
};

// Module exports
export default configureStore;

We will also need to configure Jest to ignore store configuration when running code coverage, open jest.config.json file and update with the following changes:

  {
    "collectCoverageFrom": ["src/**/*.{js,jsx,mjs}"],
    "coveragePathIgnorePatterns": [
      ...
      "<rootDir>/src/sagas/",
+     "<rootDir>/src/store/",
      "<rootDir>/src/tests/setup.js",
      ...
    ]
  }

commit: Setup Redux store

Creating Root component

Create Root folder inside src/components/core then create a component along with its test file:

mkdir src/components/core/Root && cd src/components/core/Root
touch index.jsx index.test.js

Component

To create a component, add the content below to index.jsx file:

// @flow
// Module dependencies
import * as React from 'react';

// Types
type Return = React.Node;

// Component
const Root = (): Return => <div>Root component</div>;

// Module exports
export default Root;

Tests

You may need to add tests to index.test.js file:

// Module dependencies
import { shallow } from 'enzyme';
import React from 'react';

// Components
import Root from './index';

// Tests
describe('components/core/Root', () => {
  it('should render without crashing', () => {
    shallow(<Root />);
  });
});

commit: Create Root component

Connecting Root component to Redux store

We will be configuring the Redux store available to the connect() calls in the React component hierarchy below.

To do that, open src/components/core/Root/index.jsx file and update with the following changes:

// @flow
// Module dependencies
  import * as React from 'react';
+ import { Provider } from 'react-redux';
+
+ import App from 'components/core/App';

  // Types
+ type Props = { store: any };
  type Return = React.Node;

  // Component
- const Root = (): Return => <div>Root component</div>;
+ const Root = ({ store }: Props): Return => (
+   <Provider store={store}>
+     <App />
+   </Provider>
+ );

// Module exports
export default Root;

We also need to update its tests:

  // Module dependencies
  import { shallow } from 'enzyme';
+ import { Map } from 'immutable';
  import React from 'react';
+ import configureStore from 'redux-mock-store';

  // Components
+ import App from 'components/core/App';
  import Root from './index';

  // Tests
  describe('components/core/Root', () => {
-   it('should render without crashing', () => {
-     shallow(<Root />);
-   });
+   it('should accept "store" prop', () => {
+     // Initialize mock Redux store
+     const store = configureStore();
+
+     // Mock data
+     const initialState = Map();
+
+     // Shallow rendering
+     shallow(<Root store={store(initialState)} />);
+   });
+
+   it('should render <App />', () => {
+     // Initialize mock Redux store
+     const store = configureStore();
+
+     // Mock data
+     const initialState = Map();
+
+     // Shallow rendering
+     const wrapper = shallow(<Root store={store(initialState)} />);
+
+     // Assertions
+     expect(wrapper).toContainReact(<App />);
+   });
  });

commit: Connect Root component to Redux store

Configuring project entry point & scaffold component hierarchy

Open src/index.js file and update with the following changes:

  ...
  ...

  import registerServiceWorker from 'registerServiceWorker';
  import 'dependencies';

- // Starting point component
- import App from 'components/core/App';
+ import Root from 'components/core/Root';
+
+ // Redux store
+ import configureStore from 'store/setup';

  ...
  ...

+ // Initialize Redux store
+ const store = configureStore();
+
  // Render React element into the DOM
- ReactDOM.render(<App />, root);
+ ReactDOM.render(<Root store={store} />, root);

commit: Setup project entry point & scaffold component hierarchy

Creating a Higher Order Component to convert container component’s Immutable.js props to presentational component’s JavaScript props

We need a higher order component to map the Immutable.js props in our container component to the pure JavaScript props used in our presentational component. It simply takes the Immutable.js props from our container component, and converts them using Immutable.js’s toJS() to plain JavaScript props, which are then passed to our presentational component. For more information, see What are some opinionated Best Practices for using Immutable.JS with Redux?

Let’s create toJS folder and a component file inside src/HOCs/common:

mkdir src/HOCs/common/toJS && touch src/HOCs/common/toJS/index.jsx

Component

To create a component, add the content below to index.jsx file:

// Module dependencies
import { Iterable } from 'immutable';
import React from 'react';

// Component
const toJS = WrappedComponent => (wrappedComponentProps) => {
  // Variables
  const KEY = 0;
  const VALUE = 1;

  // Map Immutable props to pure JavaScript props
  const propsJS = Object.entries(wrappedComponentProps).reduce((newProps, wrappedComponentProp) => {
    // eslint-disable-next-line
    newProps[wrappedComponentProp[KEY]] = Iterable.isIterable(wrappedComponentProp[VALUE])
      ? wrappedComponentProp[VALUE].toJS()
      : wrappedComponentProp[VALUE];
    return newProps;
  }, {});

  return <WrappedComponent {...propsJS} />;
};

// Module exports
export default toJS;

We will also need to configure Jest to ignore this HOC when running code coverage, open jest.config.json file and update with the following changes:

  {
    "collectCoverageFrom": ["src/**/*.{js,jsx,mjs}"],
    "coveragePathIgnorePatterns": [
      ...
      "<rootDir>/src/dependencies/",
+     "<rootDir>/src/HOCs/common/toJS",
      "<rootDir>/src/reducers/",
      ...
    ]
  }

commit: Create HOC to convert Immutable data structure to pure JavaScript object

Creating a reducer for root data domain

On the command line, create a reducer along with its test file:

mkdir src/data && cd src/data && touch reducers.js
mkdir __tests__ && touch __tests__/reducers.test.js

Reducer

Add the content below to src/data/reducers.js:

// @flow

// Types
type Action = { +type: string };
type State = ?Object;

// Reducer
export default (state: State = null, action: Action): State => state;

We will also need to combine this slice reducer with the current application state. To do this, open src/reducers/index.js and update with the following changes:

  ...

+ // Reducers
+ import data from 'data/reducers';
+
  // Combine reducers
  const reducer = combineReducers({
-   data: () => null
+   data
  });

  ...

Tests

You may need to add tests to index.test.js file:

// Reducers
import reducers from '../reducers';

// Tests
describe('data/reducers', () => {
  it('should return the initial state', () => {
    // Assertions
    expect(reducers(undefined, {})).toBeNull();
  });
});

commit: Define reducer for root data domain

Creating a reducer for root screens domain

On the command line, create a reducer along with its test file:

cd src/screens && touch reducers.js
mkdir __tests__ && touch __tests__/reducers.test.js

Reducer

Add the content below to src/screens/reducers.js:

// @flow

// Types
type Action = { +type: string };
type State = ?Object;

// Reducer
export default (state: State = null, action: Action): State => state;

We will also need to combine this slice reducer with the current application state. To do this, open src/reducers/index.js and update with the following changes:

  ...

  import data from 'data/reducers';
+ import screens from 'screens/reducers';

  // Combine reducers
  const reducer = combineReducers({
-   data
+   data,
+   screens
  });

  ...

Tests

You may need to add tests to index.test.js file:

// Reducers
import reducers from '../reducers';

// Tests
describe('screens/reducers', () => {
  it('should return the initial state', () => {
    // Assertions
    expect(reducers(undefined, {})).toBeNull();
  });
});

commit: Define reducer for root screens domain

Creating state models

On the command line, create models folder inside src/constants:

mkdir src/constants/models

Then, create state.js file inside models folder:

touch src/constants/models/state.js

Inside state.js, just export an empty object:

export default {};

commit: Define state models

Creating asynchronous objects

Open src/constants/models/state.js and replace the entire empty object with the following values:

export default {
  model: {
    asynchronous: {
      error: null,
      loading: false
    }
  },
  pattern: {
    asynchronous: props => ({
      state: {
        ui: {
          asynchronous: {
            error: null,
            loading: false
          }
        }
      }
    })
  },
  wrapper: {
    asynchronous: props => ({
      state: {
        ui: {
          asynchronous: { ...props }
        }
      }
    })
  }
};

commit: Add asynchronous objects to state models

Back to top

Adding React Router

A client-side route happens when the route is handled internally by the JavaScript that is loaded on the page. When a user clicks on a link, the URL changes but the request to the server is prevented. The adjustment to the URL will result in a changed state of the application. The changed state will ultimately result in a different view of the webpage. This could be the rendering of a new component, or even a request to a server for some data that the application will turn into some HTML elements.

It is important to note that the whole page won’t refresh when using client-side routing. There are just some elements inside the application that will change.

Installing React Router ecosystem

React Router

To configure client-side routing, we will be using React Router, the most popular declarative routing for React.

yarn add react-router-dom

commit: Install react-router-dom package

React Router Last Location

A utility provides access to the last location in React Router. Useful for handling internal routing. Easily prevent leaving your app by users.

yarn add react-router-last-location

commit: Install react-router-last-location package

React Router Scroll Memory

A React component to keep the scroll of the page and to restore it if the user clicks a previous button on the browser.

yarn add react-router-scroll-memory

commit: Install react-router-scroll-memory package

Library Definitions

Finally, don’t forget to update the library definitions:

yarn type:install

commit: Update library definitions

Defining paths variable for router

Create paths.js file inside src/constants/router folder:

touch src/constants/router/paths.js

Then, add a placeholder object:

export default {};

commit: Add paths variable for router

Creating Routes component

Routes is a component wrapping all routes, it uses the HTML5 history API to keep your UI in sync with the URL.

On the command line, create Routes folder inside src/components/core:

mkdir src/components/core/Routes

Then, create a component along with its test file inside Routes:

cd src/components/core/Routes && touch index.jsx index.test.js

To create a component, add the content below to index.jsx file:

// Module dependencies
import React from 'react';
import { Route, Switch } from 'react-router-dom';

// Component
const Routes = () => (
  <Switch>
    <Route path="/" render={() => <div>Home</div>} />
  </Switch>
);

// Module exports
export default Routes;

You may need to add tests to index.test.js file, the following is a simple “smoke test” verifying that a component renders without throwing:

// Module dependencies
import { shallow } from 'enzyme';
import React from 'react';

// Components
import Routes from './index';

// Tests
describe('components/core/Routes', () => {
  it('should render without crashing', () => {
    shallow(<Routes />);
  });
});

commit: Create Routes component

Back to top

Adding Bootstrap

Bootstrap is an open source toolkit for developing with HTML, CSS, and jQuery. Quickly prototype your ideas or build your entire app with its Sass variables and mixins, responsive grid system, and extensive prebuilt components.

Installation

On the command line, run the following commands:

yarn add bootstrap

commit: Install bootstrap package

yarn type:install

commit: Update library definitions

Importing compiled CSS

You can use Bootstrap’s ready-to-use CSS by simply adding compiled CSS to your project’s dependency entry point.

To do this, open src/dependencies/index.js file and add the following import statement:

+ // Bootstrap CSS
+ export * from 'bootstrap/dist/css/bootstrap.min.css';

commit: Add compiled Bootstrap CSS to the dependency entry point

Back to top

Adding Open Iconic

Open Iconic is the open source sibling of Iconic. It is a hyper-legible collection of 223 icons with a tiny footprint—ready to use with Bootstrap.

Installation

On the command line, run the following commands:

yarn add open-iconic

commit: Install open-iconic package

yarn type:install

commit: Update library definitions

Using Open Iconic’s icon font with Bootstrap

Open src/dependencies/index.js file and add the following import statement:

  // Bootstrap CSS
  export * from 'bootstrap/dist/css/bootstrap.min.css';
+
+ // Open Iconic icon font for Bootstrap
+ export * from 'open-iconic/font/css/open-iconic-bootstrap.min.css';

commit: Add compiled Open Iconic CSS to the dependency entry point

Back to top

Adding a Global Stylesheet

Creating Sass structure

To create a global Sass’s folder structure, run the following commands in the terminal:

mkdir src/styles && cd src/styles
mkdir base components content mixins placeholders utilities vendor && touch index.scss
cd base && touch _settings.scss && cd ..
cd vendor && mkdir frameworks plugins

Note: you may need to add placeholder styles to index.scss and _settings.scss files in order to pass stylesheet linting.

After creation, your folder structure should look like this:

styles
├── base
│   └── _settings.scss
├── componentss
├── content
├── mixins
├── placeholders
├── utilities
├── vendor
│   ├── frameworks
│   └── plugins
└── index.scss

commit: Create global stylesheet structure

Creating Sass starting point

With this approach, Sass will take the file that we want to import and combine it with the file we’re importing into so we can serve a single Sass file to the project starting point.

Let’s combine all partials in one place which is a global Sass starting point, open src/styles/index.scss then add the following import statements:

- // Empty styles
+ // Base
+ @import './base/settings';

commit: Setup Sass starting point

Applying global styles

Last thing we need to do is to import the global Sass starting point to the project starting point.

To do that, open src/index.jsx file and add the following import statement next to the list of module dependencies:

  // Module dependencies
  ...
  ...

+ // Styles
+ import './styles/index.scss';

commit: Add global styles to the project starting point

Back to top

Adding Sass Boilerplate

Let’s add minimal styles to kickstart a responsive React app.

Variables

Open src/styles/base/_settings.scss file and add the global variables as follows:

// Breakpoints
$breakpoint-sm: 576px;
$breakpoint-md: 768px;
$breakpoint-lg: 992px;
$breakpoint-xl: 1200px;

// Screen sizes
$screen-xs: 320px;
$screen-sm: 576px;
$screen-md: 768px;
$screen-lg: 992px;
$screen-xl: 1200px;

// Layout
$footer-height-xs: 145px;
$footer-height-sm: 175px;
$header-height-xs: 35px;
$header-height-sm: 45px;

commit: Add generic Sass variables

Mixins

Clearfix

On the command line, create _clearfix.scss file inside src/styles/mixins:

touch src/styles/mixins/_clearfix.scss

Then, add a clearfix mixin:

@mixin clearfix {
  &::after {
    clear: both;
    content: '';
    display: block;
  }
}

And add the following import statement to the Sass starting point:

  // Base
  @import './base/settings';

+ // Mixins
+ @import './mixins/clearfix';

commit: Add clearfix mixin

Transition

On the command line, create _transition.scss file inside src/styles/mixins:

touch src/styles/mixins/_transition.scss

Then, add a transition mixin:

// Link transition
@mixin transition-link {
  transition: color 0.2s ease-in-out;
}

And add the following import statement to the Sass starting point:

  ...
  ...

  // Mixins
  @import './mixins/clearfix';
+ @import './mixins/transition';

commit: Add link transition mixin

Utilities

Visibility

On the command line, create _visibility.scss file inside src/styles/utilities:

touch src/styles/utilities/_visibility.scss

Then, add generic visibility styles:

:global(.visible) {
  visibility: visible;
}

:global(.invisible) {
  visibility: hidden;
}

And add the following import statement to the Sass starting point:

  ...
  @import './mixins/transition';

+ // Utilities
+ @import './utilities/visibility';

commit: Add generic visibility styles

Content

Typography

On the command line, create _typography.scss file inside src/styles/content:

touch src/styles/content/_typography.scss

Then, add generic typography styles:

:global(.headline) {
  font-size: 1.5rem;
}

:global(.subheadline) {
  font-size: 1.25rem;
}

:global(.text-block) {
  display: block;
}

:global(.text-end) {
  margin-bottom: 0;
}

And add the following import statement to the Sass starting point:

  ...
  @import './mixins/transition';

+ // Content
+ @import './content/typography';

commit: Add generic typography styles

Components

Buttons

On the command line, create _buttons.scss file inside src/styles/components:

touch src/styles/components/_buttons.scss

Then, add generic button styles:

// Button wrapper
:global(.button-wrapper) {
  $element-spacing: 0.25rem;

  > :not(:last-child) {
    margin-right: $element-spacing;
  }

  > :not(:first-child) {
    margin-left: $element-spacing;
  }
}

And add the following import statement to the Sass starting point:

  ...
  ...

  // Utilities
  @import './utilities/visibility';
+
+ // Components
+ @import './components/buttons';

commit: Add generic button styles

Back to top

Adding a Layout Boilerplate

On the command line, create _layout.scss file inside src/styles/base:

touch src/styles/base/_layout.scss

Then, add base layout styles to html and body as follows:

// HTML
html {
  font-size: 87.5%;
  min-height: 100%;
  position: relative;
}

// Body
body {
  font-size: 1rem;
  min-width: $screen-xs;
}

And add the following import statement to the Sass starting point:

  // Base
  @import './base/settings';
+ @import './base/layout';

commit: Add base layout to main HTML elements

Back to top

Adding Common Components

Grid

On the command line, create Grid folder inside src/components/common:

mkdir src/components/common/Grid

Then, create an index file inside Grid:

touch src/components/common/Grid/index.js

commit: Create Grid index

Container

Create Container folder inside Grid:

mkdir src/components/common/Grid/Container

Then, create a component along with its test file inside Container:

cd src/components/common/Grid/Container
touch index.jsx index.test.js

Component

To create a component, add the content below to index.jsx file:

// @flow
// Module dependencies
import * as React from 'react';

// Types
type Props = { children: React.Node };
type Return = React.Node;

// Component
const Container = ({ children }: Props): Return => <div className="container">{children}</div>;

// Module exports
export default Container;

Tests

You may need to add tests to index.test.js file:

// Module dependencies
import { shallow } from 'enzyme';
import React from 'react';

// Components
import Container from './index';

// Tests
describe('components/common/Grid/Container', () => {
  it('should render children', () => {
    // Mock data
    const children = <div>Content</div>;

    // Shallow rendering
    const wrapper = shallow(<Container>{children}</Container>);

    // Assertions
    expect(wrapper).toContainReact(children);
  });

  it('should have a correct class name', () => {
    // Mock data
    const children = <div>Content</div>;
    const className = 'container';

    // Shallow rendering
    const wrapper = shallow(<Container>{children}</Container>);

    // Assertions
    expect(wrapper.prop('className')).toEqual(className);
  });
});

Index

Next, add the export statement below to the index file:

// eslint-disable-next-line
export { default as Container } from './Container';

commit: Create Container component

Row

Create Row folder inside Grid:

mkdir src/components/common/Grid/Row

Then, create a component along with its test file inside Row:

cd src/components/common/Grid/Row
touch index.jsx index.test.js

Component

To create a component, add the content below to index.jsx file:

// @flow
// Module dependencies
import cx from 'classnames';
import * as React from 'react';

// Types
type Props = {
  alignment?: string,
  children: React.Node
};
type Return = React.Node;

// Default props
const defaultProps = {
  alignment: null
};

// Component
const Row = ({ alignment, children }: Props): Return => (
  <div className={cx('row', alignment)}>{children}</div>
);

// Specify default values for props
Row.defaultProps = defaultProps;

// Module exports
export default Row;

Tests

You may need to add tests to index.test.js file:

// Module dependencies
import { shallow } from 'enzyme';
import React from 'react';

// Components
import Row from './index';

// Tests
describe('components/common/Grid/Row', () => {
  it('should render children', () => {
    // Mock data
    const children = <div>Content</div>;

    // Shallow rendering
    const wrapper = shallow(<Row>{children}</Row>);

    // Assertions
    expect(wrapper).toContainReact(children);
  });

  it('should have a correct class name', () => {
    // Mock data
    const children = <div>Content</div>;

    // Shallow rendering
    const wrapper = shallow(<Row>{children}</Row>);

    // Assertions
    expect(wrapper.prop('className')).toEqual('row');
  });

  it('should accept "alignment" prop', () => {
    // Mock data
    const alignment = 'justify-content-center';
    const children = <div>Content</div>;

    // Shallow rendering
    const wrapper = shallow(<Row alignment={alignment}>{children}</Row>);

    // Assertions
    expect(wrapper.prop('className')).toEqual(`row ${alignment}`);
  });
});

Index

Next, add the export statement below to the index file:

- // eslint-disable-next-line
  export { default as Container } from './Container';
+ export { default as Row } from './Row';

commit: Create Row component

Column

Create Column folder inside Grid:

mkdir src/components/common/Grid/Column

Then, create a component along with its test file inside Column:

cd src/components/common/Grid/Column
touch index.jsx index.test.js

Component

To create a component, add the content below to index.jsx file:

// @flow
// Module dependencies
import * as React from 'react';

// Types
type Props = {
  children: React.Node,
  size?: string
};
type Return = React.Node;

// Default props
const defaultProps = {
  size: 'col'
};

// Component
const Column = ({ children, size }: Props): Return => <div className={size}>{children}</div>;

// Specify default values for props
Column.defaultProps = defaultProps;

// Module exports
export default Column;

Tests

You may need to add tests to index.test.js file:

// Module dependencies
import { shallow } from 'enzyme';
import React from 'react';

// Components
import Column from './index';

// Tests
describe('components/common/Grid/Column', () => {
  it('should render children', () => {
    // Mock data
    const children = <div>Content</div>;

    // Shallow rendering
    const wrapper = shallow(<Column>{children}</Column>);

    // Assertions
    expect(wrapper).toContainReact(children);
  });

  it('should render default column width', () => {
    // Mock data
    const children = <div>Content</div>;
    const size = 'col';

    // Shallow rendering
    const wrapper = shallow(<Column>{children}</Column>);

    // Assertions
    expect(wrapper.prop('className')).toEqual(size);
  });

  it('should accept "size" prop', () => {
    // Mock data
    const children = <div>Content</div>;
    const size = 'col-sm-12';

    // Shallow rendering
    const wrapper = shallow(<Column size={size}>{children}</Column>);

    // Assertions
    expect(wrapper.prop('className')).toEqual(size);
  });
});

Index

Next, add the export statement below to the index file:

+ export { default as Column } from './Column';
  export { default as Container } from './Container';
  export { default as Row } from './Row';

commit: Create Column component

Layout

On the command line, create Layout folder inside src/components/common:

mkdir src/components/common/Layout

Then, create a component along with its test file inside Layout:

cd src/components/common/Layout
touch index.jsx index.test.js

Component

To create a component, add the content below to index.jsx file:

// @flow
// Module dependencies
import * as React from 'react';

import { Column, Row } from 'components/common/Grid';

// Types
type Props = {
  children: React.Node,
  size?: string
};
type Return = React.Node;

// Default props
const defaultProps = {
  size: 'col-md-10 col-lg-8'
};

// Component
const Layout = ({ children, size }: Props): Return => (
  <Row alignment="justify-content-sm-center">
    <Column size={size}>{children}</Column>
  </Row>
);

// Specify default values for props
Layout.defaultProps = defaultProps;

// Module exports
export default Layout;

Tests

You may need to add tests to index.test.js file:

// Module dependencies
import { shallow } from 'enzyme';
import React from 'react';

// Components
import { Column, Row } from 'components/common/Grid';
import Layout from './index';

// Tests
describe('components/common/Layout', () => {
  it('should render children', () => {
    // Mock data
    const children = <div>Content</div>;

    // Shallow rendering
    const wrapper = shallow(<Layout>{children}</Layout>);

    // Assertions
    expect(wrapper).toContainReact(children);
  });

  it('should contain a correct layout structure', () => {
    // Mock data
    const children = <div>Content</div>;

    // Shallow rendering
    const wrapper = shallow(<Layout>{children}</Layout>);

    // Assertions
    expect(wrapper.find(Row)).toHaveLength(1);
    expect(wrapper.find(Row).find(Column)).toHaveLength(1);
  });

  it('should contain <Row /> component with default "alignment" prop', () => {
    // Mock data
    const children = <div>Content</div>;
    const alignment = 'justify-content-sm-center';

    // Shallow rendering
    const wrapper = shallow(<Layout>{children}</Layout>);

    // Assertions
    expect(wrapper.find(Row).prop('alignment')).toEqual(alignment);
  });

  it('should accept "size" prop', () => {
    // Mock data
    const children = <div>Content</div>;
    const size = 'col-sm-12';

    // Shallow rendering
    const wrapper = shallow(<Layout size={size}>{children}</Layout>);

    // Assertions
    expect(wrapper.find(Column).prop('size')).toEqual(size);
  });
});

commit: Create Layout component

Page

On the command line, create Page folder inside src/components/common:

mkdir src/components/common/Page

Then, create an index file inside Page:

touch src/components/common/Page/index.js

commit: Create Page index

Document

Create Document folder inside Page:

mkdir src/components/common/Page/Document

Then, create a component along with its test file inside Document:

cd src/components/common/Page/Document
touch index.jsx index.test.js

Component

To create a component, add the content below to index.jsx file:

// @flow
// Module dependencies
import * as React from 'react';

// Types
type Props = { children: React.Node };
type Return = React.Node;

// Component
export const Document = ({ children }: Props): Return => children;

// Module exports
export default Document;

Tests

You may need to add tests to index.test.js file:

// Module dependencies
import { shallow } from 'enzyme';
import React from 'react';

// Components
import Document from './index';

// Tests
describe('components/common/Page/Document', () => {
  it('should render children', () => {
    // Mock data
    const children = <div>Content</div>;

    // Shallow rendering
    const wrapper = shallow(<Document>{children}</Document>);

    // Assertions
    expect(wrapper).toContainReact(children);
  });
});

Index

Next, add the export statement below to the index file:

// eslint-disable-next-line
export { default as Document } from './Document';

commit: Create Document component

Body

Create Body folder inside Page:

mkdir src/components/common/Page/Body

Then, create a component along with its test file inside Body:

cd src/components/common/Page/Body
touch index.jsx index.test.js

Component

To create a component, add the content below to index.jsx file:

// @flow
// Module dependencies
import * as React from 'react';

// Types
type Props = { children: React.Node };
type Return = React.Node;

// Component
const Body = ({ children }: Props): Return => children;

// Module exports
export default Body;

Tests

You may need to add tests to index.test.js file:

// Module dependencies
import { shallow } from 'enzyme';
import React from 'react';

// Components
import Body from './index';

// Tests
describe('components/common/Page/Body', () => {
  it('should render children', () => {
    // Mock data
    const children = <div>Content</div>;

    // Shallow rendering
    const wrapper = shallow(<Body>{children}</Body>);

    // Assertions
    expect(wrapper).toContainReact(children);
  });
});

Index

Next, add the export statement below to the index file:

- // eslint-disable-next-line
+ export { default as Body } from './Body';
  export { default as Document } from './Document';

commit: Create Body component

Head

Create Head folder inside Page:

mkdir src/components/common/Page/Head

Then, create a component along with its test file inside Head:

cd src/components/common/Page/Head
touch index.jsx index.test.js

Component

To create a component, add the content below to index.jsx file:

// @flow
// Module dependencies
import * as React from 'react';

// Types
type Props = { children: React.Node };
type Return = React.Node;

// Component
const Head = ({ children }: Props): Return => children;

// Module exports
export default Head;

Tests

You may need to add tests to index.test.js file:

// Module dependencies
import { shallow } from 'enzyme';
import React from 'react';

// Components
import Head from './index';

// Tests
describe('components/common/Page/Head', () => {
  it('should render children', () => {
    // Mock data
    const children = <div>Content</div>;

    // Shallow rendering
    const wrapper = shallow(<Head>{children}</Head>);

    // Assertions
    expect(wrapper).toContainReact(children);
  });
});

Index

Next, add the export statement below to the index file:

  export { default as Body } from './Body';
  export { default as Document } from './Document';
+ export { default as Head } from './Head';

commit: Create Head component

Title

Create Title folder inside Page:

mkdir src/components/common/Page/Title

Then, create a component along with its test file inside Title:

cd src/components/common/Page/Title
touch index.jsx index.test.js

Dependencies

You may need to dynamically update the page title based on the content or from React component, you can use React Helmet, a third party library.

React Helmet is a document head manager for React. It takes plain HTML tags and outputs plain HTML tags. It is so simple, and React beginner friendly.

Let’s install the library as a depencency:

yarn add react-helmet

commit : Install react-helmet package

yarn type:install

commit: Update library definitions

Component

To create a component, add the content below to index.jsx file:

// @flow
// Module dependencies
import * as React from 'react';
import { Helmet } from 'react-helmet';

// Types
type Props = { children: React.Node };
type Render = React.Node;

// Component
export const Title = ({ children }: Props): Render => (
  <Helmet>
    <title>{children}</title>
  </Helmet>
);

// Module exports
export default Title;

Tests

You may need to add tests to index.test.js file:

// Module dependencies
import { shallow } from 'enzyme';
import React from 'react';

// Components
import Title from './index';

// Tests
describe('components/common/Page/Title', () => {
  it('should render children', () => {
    // Mock data
    const text = 'Page title';

    // Shallow rendering
    const wrapper = shallow(<Title>{text}</Title>);

    // Assertions
    expect(wrapper.find('title').text()).toEqual(text);
  });
});

Index

Next, add the export statement below to the index file:

  export { default as Body } from './Body';
  export { default as Document } from './Document';
  export { default as Head } from './Head';
+ export { default as Title } from './Title';

commit: Create Title component

Spinner

On the command line, create Spinner folder inside src/components/common:

mkdir src/components/common/Spinner

Then, create a component along with its test file and stylesheets inside Spinner:

cd src/components/common/Spinner
touch index.jsx index.test.js styles.scss

Component

To create a component, add the content below to index.jsx file:

// @flow
// Module dependencies
import * as React from 'react';

// Styles
import './styles.scss';

// Types
type Return = React.Node;

// Component
const Spinner = (): Return => <div styleName="spinner">Loading...</div>;

// Module exports
export default Spinner;

Tests

You may need to add tests to index.test.js file:

// Module dependencies
import { shallow } from 'enzyme';
import React from 'react';

// Components
import Spinner from './index';

// Tests
describe('components/common/Spinner', () => {
  it('should render a correct output', () => {
    // Mock data
    const data = 'Loading...';

    // Shallow rendering
    const wrapper = shallow(<Spinner />);

    // Assertions
    expect(wrapper.text()).toEqual(data);
  });
});

Styles

Let’s add a minimal amount of styles to the component as below:

// Container
.container {
  display: block;
}

commit: Create Spinner component

ExLink

On the command line, create ExLink folder inside src/components/common:

mkdir src/components/common/ExLink

Then, create a component along with its test file inside ExLink:

cd src/components/common/ExLink
touch index.jsx index.test.js

Component

To create a component, add the content below to index.jsx file:

// @flow
// Module dependencies
import * as React from 'react';

// Types
type Props = {
  children: React.Node,
  replace?: boolean,
  styles?: string,
  to: string
};
type Return = React.Node;

// Default props
const defaultProps = {
  replace: false,
  styles: 'link'
};

// Component
const ExLink = (props: Props): Return => {
  const {
    children, replace, styles, to
  } = props;

  return (
    <a className={styles} href={to} rel="noopener noreferrer" target={replace ? '_self' : '_blank'}>
      {children}
    </a>
  );
};

// Specify default values for props
ExLink.defaultProps = defaultProps;

// Module exports
export default ExLink;

Tests

You may need to add tests to index.test.js file:

// Module dependencies
import { shallow } from 'enzyme';
import React from 'react';

// Components
import ExLink from './index';

// Tests
describe('components/common/ExLink', () => {
  it('should render children', () => {
    // Mock data
    const children = <div>Content</div>;

    // Shallow rendering
    const wrapper = shallow(<ExLink>{children}</ExLink>);

    // Assertions
    expect(wrapper).toContainReact(children);
  });

  it('should render a correct attribute value', () => {
    // Mock data
    const children = 'External link';
    const rel = 'noopener noreferrer';

    // Shallow rendering
    const wrapper = shallow(<ExLink>{children}</ExLink>);

    // Assertions
    expect(wrapper.prop('rel')).toEqual(rel);
  });

  it('should render default "target" attribute', () => {
    // Mock data
    const children = 'External link';
    const target = '_blank';

    // Shallow rendering
    const wrapper = shallow(<ExLink>{children}</ExLink>);

    // Assertions
    expect(wrapper.prop('target')).toEqual(target);
  });

  it('should render default "class" attribute', () => {
    // Mock data
    const children = 'External link';
    const style = 'link';

    // Shallow rendering
    const wrapper = shallow(<ExLink>{children}</ExLink>);

    // Assertions
    expect(wrapper.prop('className')).toEqual(style);
  });

  it('should accept "replace" prop', () => {
    // Mock data
    const children = 'External link';
    const replace = true;
    const target = '_self';

    // Shallow rendering
    const wrapper = shallow(<ExLink replace={replace}>{children}</ExLink>);

    // Assertions
    expect(wrapper.prop('target')).toEqual(target);
  });

  it('should accept "to" prop', () => {
    // Mock data
    const children = 'External link';
    const to = 'https://setup-react-app.herokuapp.com';

    // Shallow rendering
    const wrapper = shallow(<ExLink to={to}>{children}</ExLink>);

    // Assertions
    expect(wrapper.prop('href')).toEqual(to);
  });

  it('should accept "styles" prop', () => {
    // Mock data
    const children = 'External link';
    const styles = 'nav-link';

    // Shallow rendering
    const wrapper = shallow(<ExLink styles={styles}>{children}</ExLink>);

    // Assertions
    expect(wrapper.prop('className')).toEqual(styles);
  });
});

commit: Create ExLink component(1), (2)

Back to top

Adding Core Components

Wrapper

Wrapper is an element containing everything, including elements with fixed positioning.

On the command line, create Wrapper folder inside src/components/core:

mkdir src/components/core/Wrapper

Then, create a component along with its test file and stylesheets inside Wrapper:

cd src/components/core/Wrapper
touch index.jsx index.test.js styles.scss

Temporary solution

Please note that this is a tempolary solution to prevent FOUC with CSS modules. So, let’s create a helper function to help with this issue.

Create utilities.js file inside src/helpers:

touch src/helpers/utilities.js

Then, add the content below:

// @flow

// Deley
// eslint-disable-next-line
export const delay = (callback: Function, duration: number) => {
  setTimeout(() => {
    callback();
  }, duration);
};

commit: Create delay helper function

Before creating a commponent, we need to specify wrapper property to src/constants/elements/html.js file:

  export default {
-  root: 'root'
+  root: 'root',
+  wrapper: 'wrapper'
  };

Components

To create a component, add the content below to index.jsx file:

// @flow
// Module dependencies
import * as React from 'react';

import { delay } from 'helpers/utilities';

// Constants
import HTML from 'constants/elements/html';

// Styles
import './styles.scss';

// Types
type Props = { children: React.Node };
type Return = React.Node;
type State = { visibility: string };

// Component
class Wrapper extends React.Component<Props, State> {
  // Initial state
  state = { visibility: 'invisible' };

  // After a component is mounted...
  componentDidMount() {
    // Set visibility after FOUC has gone
    delay(this.onVisible, 200);
  }

  // Set visibility
  onVisible = () => {
    this.setState(() => ({ visibility: 'visible' }));
  };

  // Render component
  render(): Return {
    return (
      <div className={this.state.visibility} id={HTML.wrapper} styleName="container">
        {this.props.children}
      </div>
    );
  }
}

// Module exports
export default Wrapper;

Tests

You may need to add tests to index.test.js file:

// Module dependencies
import { shallow } from 'enzyme';
import React from 'react';

// Components
import Wrapper from './index';

// Tests
describe('components/core/Wrapper', () => {
  it('should render children', () => {
    // Mock data
    const children = <div>Content</div>;

    // Shallow rendering
    const wrapper = shallow(<Wrapper>{children}</Wrapper>);

    // Assertions
    expect(wrapper).toContainReact(children);
  });
});

Styles

Let’s add a minimal amount of styles to the component as below:

// Global styles
@import '../../../styles/base/settings';

// Container
.container {
  margin-bottom: $footer-height-xs;
  margin-top: $header-height-xs;

  @media (min-width: $breakpoint-sm) {
    margin-bottom: $footer-height-sm;
    margin-top: $header-height-sm;
  }
}

commit: Create Wrapper component

Content

Content is an element wrapping the rest of the content on our page except elements with fixed positioning.

On the command line, create Content folder inside src/components/core:

mkdir src/components/core/Content

Then, create a component along with its test file inside Content:

cd src/components/core/Content
touch index.jsx index.test.js

Before creating a commponent, you need to specify content property to src/constants/elements/html.js file:

  export default {
+  content: 'content',
   root: 'root',
   wrapper: 'wrapper'
  };

Component

To create a component, add the content below to index.jsx file:

// @flow
// Module dependencies
import * as React from 'react';

// Constants
import HTML from 'constants/elements/html';

// Types
type Props = { children: React.Node };
type Return = React.Node;

// Component
const Content = ({ children }: Props): Return => <div id={HTML.content}>{children}</div>;

// Module exports
export default Content;

Tests

You may need to add tests to index.test.js file:

// Module dependencies
import { shallow } from 'enzyme';
import React from 'react';

// Components
import Content from './index';

// Tests
describe('components/core/Content', () => {
  it('should render children', () => {
    // Mock data
    const children = <div>Content</div>;

    // Shallow rendering
    const wrapper = shallow(<Content>{children}</Content>);

    // Assertions
    expect(wrapper).toContainReact(children);
  });
});

commit: Create Content component

Header

On the command line, create Header folder inside src/components/core:

mkdir src/components/core/Header

Then, create a component along with its test file and stylesheets inside Header:

cd src/components/core/Header
touch index.jsx index.test.js styles.scss

Component

To create a component, add the content below to index.jsx file:

// @flow
// Module dependencies
import * as React from 'react';

import { Column, Container, Row } from 'components/common/Grid';

// Styles
import './styles.scss';

// Types
type Return = React.Node;

// Component
const Header = (): Return => (
  <header styleName="container">
    <Container>
      <Row>
        <Column>
          <div styleName="brand">Setup React App</div>
        </Column>
      </Row>
    </Container>
  </header>
);

// Module exports
export default Header;

Tests

You may need to add tests to index.test.js file, the following is a simple “smoke test” verifying that a component renders without throwing:

// Module dependencies
import { shallow } from 'enzyme';
import React from 'react';

// Components
import Header from './index';

// Tests
describe('components/core/Header', () => {
  it('should render without crashing', () => {
    shallow(<Header />);
  });
});

Styles

Let’s add a minimal amount of styles to the component as below:

// Global styles
@import '../../../styles/base/settings';

// Container
.container {
  align-items: center;
  background-color: #343a40;
  display: flex;
  height: $header-height-xs;
  left: 0;
  position: fixed;
  right: 0;
  top: 0;
  z-index: 1;

  @media (min-width: $breakpoint-sm) {
    height: $header-height-sm;
  }
}

// Branding
.brand { /* TODO */ }

commit: Create Header component

Main

On the command line, create Main folder inside src/components/core:

mkdir src/components/core/Main

Then, create a component along with its test file and stylesheets inside Main:

cd src/components/core/Main
touch index.jsx index.test.js styles.scss

Component

To create a component, add the content below to index.jsx file:

// @flow
// Module dependencies
import * as React from 'react';

import { Column, Container, Row } from 'components/common/Grid';

// Styles
import './styles.scss';

// Types
type Props = { children: React.Node };
type Return = React.Node;

// Component
const Main = ({ children }: Props): Return => (
  <main styleName="container">
    <Container>
      <Row>
        <Column>{children}</Column>
      </Row>
    </Container>
  </main>
);

// Module exports
export default Main;

Tests

You may need to add tests to index.test.js file:

// Module dependencies
import { shallow } from 'enzyme';
import React from 'react';

// Components
import Main from './index';

// Tests
describe('components/core/Main', () => {
  it('should render children', () => {
    // Mock data
    const children = <div>Content</div>;

    // Shallow rendering
    const wrapper = shallow(<Main>{children}</Main>);

    // Assertions
    expect(wrapper).toContainReact(children);
  });
});

Styles

Let’s add a minimal amount of styles to the component as below:

// Container
.container {
  padding-top: 1.5rem;
}

commit: Create Main component

Footer

On the command line, create Footer folder inside src/components/core:

mkdir src/components/core/Footer

Then, create a component along with its test file and stylesheets inside Footer:

cd src/components/core/Footer
touch index.jsx index.test.js styles.scss

Component

To create a component, add the content below to index.jsx file:

// @flow
// Module dependencies
import * as React from 'react';

import ExLink from 'components/common/ExLink';
import { Column, Container, Row } from 'components/common/Grid';

// Constants
import URLs from 'constants/router/urls';

// Styles
import './styles.scss';

// Types
type Return = React.Node;

// Component
const Footer = (): Return => (
  <footer styleName="container">
    <Container>
      <Row>
        <Column>
          <ul styleName="navigation">
            <li>
              <ExLink to={`https://github.com/${URLs.repo}`}>View on GitHub</ExLink>
            </li>
          </ul>
          <div styleName="ci-status">
            <div className="button-wrapper">
              <ExLink to={`https://travis-ci.org/${URLs.repo}`}>
                <img
                  alt="Build Status"
                  src={`https://travis-ci.org/${URLs.repo}.svg?branch=master`}
                />
              </ExLink>
              <ExLink to={`https://coveralls.io/github/${URLs.repo}?branch=master`}>
                <img
                  src={`https://coveralls.io/repos/github/${URLs.repo}/badge.svg?branch=master`}
                  alt="Coverage Status"
                />
              </ExLink>
            </div>
          </div>
          <div styleName="legal">
            <p>
              Designed &amp; built with all the love in{' '}
              <ExLink to="https://reactjs.org">React</ExLink> &amp;{' '}
              <ExLink to="https://redux.js.org">Redux</ExLink>.
            </p>
            <p>
              Copyright © 2018{' '}
              <ExLink to="https://github.com/rxseven">Theerawat Pongsupawat</ExLink>.
            </p>
          </div>
        </Column>
      </Row>
    </Container>
  </footer>
);

// Module exports
export default Footer;

Tests

You may need to add tests to index.test.js file, the following is a simple “smoke test” verifying that a component renders without throwing:

// Module dependencies
import { shallow } from 'enzyme';
import React from 'react';

// Components
import Footer from './index';

// Tests
describe('components/core/Footer', () => {
  it('should render without crashing', () => {
    shallow(<Footer />);
  });
});

Styles

We will be implementing "sticky footer". The purpose of a sticky footer is that it "sticks" to the bottom of the browser window. But not always, if there is enough content on the page to push the footer lower, it still does that. But if the content on the page is short, a sticky footer will still hang to the bottom of the browser window.

Let’s add styles to the component as below:

// Global styles
@import '../../../styles/base/settings';
@import '../../../styles/mixins/transition';

// Variables
$color-bright: #fff;
$color-light: #999;

// Container
.container {
  background-color: #343a40;
  bottom: 0;
  color: #999;
  height: $footer-height-xs;
  left: 0;
  padding-top: 1rem;
  position: absolute;
  right: 0;
  text-align: center;

  a {
    @include transition-link;

    &:hover {
      text-decoration: none;
    }
  }

  @media (min-width: $breakpoint-sm) {
    height: $footer-height-sm;
    padding-top: 1.75rem;
    text-align: left;
  }
}

// Navigation
.navigation {
  list-style: none;
  margin: 0 0 1.15rem 0;
  padding: 0;

  @media (min-width: $breakpoint-sm) {
    margin-bottom: 1.5rem;
  }

  > li {
    display: inline;
    font-size: 0.85rem;

    & + li {
      margin-left: 1rem;
    }

    @media (min-width: $breakpoint-sm) {
      font-size: 0.9rem;
    }
  }

  :global(.link) {
    color: $color-bright;

    &:hover {
      color: $color-light;
    }
  }
}

// Continuous Integration
.ci-status {
  margin-bottom: 1.15rem;

  @media (min-width: $breakpoint-sm) {
    margin-bottom: 1.5rem;
  }
}

// Legal
.legal {
  font-size: 0.8rem;

  @media (min-width: $breakpoint-sm) {
    font-size: 0.875rem;
  }

  p {
    margin-bottom: 0.25rem;

    &:last-of-type {
      margin-bottom: 0;
    }

    @media (min-width: $breakpoint-sm) {
      margin-bottom: 0.35rem;
    }
  }

  :global(.link) {
    color: $color-light;

    &:hover {
      color: $color-bright;
    }
  }
}

commit: Create Footer component

Back to top

Adding Main Screens

Home page

On the command line, create Home folder inside src/screens/main:

mkdir src/screens/main/Home

Then, create a component along with its test file and stylesheets inside Home:

cd src/screens/main/Home
touch index.jsx index.test.js styles.scss

Component

To create a screen component, add the content below to index.jsx file:

// @flow
// Module dependencies
import * as React from 'react';

import ExLink from 'components/common/ExLink';
import Layout from 'components/common/Layout';
import { Body, Document, Head, Title } from 'components/common/Page';

// Constants
import URLs from 'constants/router/urls';

// Styles
import './styles.scss';

// Types
type Return = React.Node;

// Component
const Home = (): Return => (
  <Document>
    <Head>
      <Title>Setup React App</Title>
    </Head>
    <Body>
      <Layout>
        <h2 className="headline">Setup React App</h2>
        <p>
          A minimal React &amp; Redux boilerplate with best practices bootstrapped with{' '}
          <ExLink to="https://github.com/facebookincubator/create-react-app">
            Create React App
          </ExLink>. It can save you a lot of time and energy searching for highly scalable
          solutions with a nice development experience to get started.
        </p>
        <div styleName="footnote">
          <div className="button-wrapper">
            <ExLink styles="btn btn-primary btn-sm" to={`https://github.com/${URLs.repo}`}>
              View on GitHub
            </ExLink>
          </div>
        </div>
      </Layout>
    </Body>
  </Document>
);

// Module exports
export default Home;

Tests

You may need to add tests to index.test.js file, the following is a simple “smoke test” verifying that a component renders without throwing:

// Module dependencies
import { shallow } from 'enzyme';
import React from 'react';

// Components
import Home from './index';

// Tests
describe('screens/main/Home', () => {
  it('should render without crashing', () => {
    shallow(<Home />);
  });
});

Styles

Let’s add a minimal amount of styles to the component as below:

// Footnote
.footnote {
  border-top: 1px solid rgba(0, 0, 0, 0.1);
  padding-top: 1rem;
}

commit: Create Home screen

Route

Open src/constants/router/paths.js file and add new route property for home screen (root route):

- export default {};
+ export default {
+   main: {
+     home: '/'
+   }
+ };

Then, open src/components/core/Routes/index.jsx file and define a root route:

+ // Screens
+ import Home from 'screens/main/Home';
+
+ // Constants
+ import PATHS from 'constants/router/paths';
+
  // Component
  const Routes = () => (
    <Switch>
-     <Route path="/" render={() => <div>Home</div>} />
+     <Route component={Home} exact path={PATHS.main.home} />
    </Switch>
  );

commit: Add route to Home screen (for more information about the change, see comment on GitHub)

Navigation link

Open src/components/core/Header/index.jsx and replace a branding text with the following NavLink:

  ...
  import * as React from 'react';
+ import { NavLink } from 'react-router-dom';

  ...

+ // Constants
+ import PATHS from 'constants/router/paths';

  ...

  // Component
  const Header = (): Return => (
    <header styleName="container">
      <Container>
        <Row>
          <Column>
-           <div styleName="brand">Setup React App</div>
+           <div styleName="brand">
+             <NavLink activeClassName="active" exact styleName="link" to={PATHS.main.home}>
+               Setup React App
+             </NavLink>
+           </div>
          </Column>
        </Row>
      </Container>
    </header>
  );

  ...
  ...

Then, open src/components/core/Header/styles.scss and add styles for a navigation link:

  // Global styles
  @import '../../../styles/base/settings';
+ @import '../../../styles/mixins/transition';
+
+ // Variables
+ $color-bright: #fff;
+ $color-light: rgba(255, 255, 255, 0.5);

  ...
  ...

  // Branding
- .brand {}
+ .brand {
+   > .link {
+     @include transition-link;
+     color: $color-bright;
+
+     &:hover {
+       color: $color-light;
+       text-decoration: none;
+     }
+
+     &:global(.active) {
+       color: $color-light;
+       cursor: default;
+     }
+   }
+ }

commit: Add navigation link to Home screen

Page not found (404)

You can display a custom 404 error page when people try to access non-existent pages on your React app.

On the command line, create 404 folder inside src/screens/main:

mkdir src/screens/main/404

Then, create a component along with its test file inside 404:

cd src/screens/main/404
touch index.jsx index.test.js

Component

To create a screen component, add the content below to index.jsx file:

// @flow
// Module dependencies
import * as React from 'react';
import { Link } from 'react-router-dom';

import Layout from 'components/common/Layout';
import { Body, Document, Head, Title } from 'components/common/Page';

// Constants
import PATHS from 'constants/router/paths';

// Types
type Return = React.Node;

// Component
const NotFound = (): Return => (
  <Document>
    <Head>
      <Title>Page not found</Title>
    </Head>
    <Body>
      <Layout>
        <div className="card">
          <div className="card-body">
            <h5 className="card-title">404</h5>
            <p className="card-text">This is not web page you are looking for.</p>
            <Link className="card-link" to={PATHS.main.home}>
              Go back to Home page
            </Link>
          </div>
        </div>
      </Layout>
    </Body>
  </Document>
);

// Module exports
export default NotFound;

Tests

You may need to add tests to index.test.js file, the following is a simple “smoke test” verifying that a component renders without throwing:

// Module dependencies
import { shallow } from 'enzyme';
import React from 'react';

// Components
import NotFound from './index';

// Tests
describe('screens/main/404', () => {
  it('should render without crashing', () => {
    shallow(<NotFound />);
  });
});

commit: Create 404 screen

Route

Then, open src/components/core/Routes/index.jsx file and define a 404 route:

  // Screens
  ...
+ import NotFound from 'screens/main/404';

  ...

  // Component
  const Routes = () => (
    <Switch>
      <Route component={Home} exact path={PATHS.main.home} />
+     <Route component={NotFound} />
    </Switch>
  );

commit: Add route to 404 screen

Navigation link

Open src/screens/main/Home/index.jsx and add a 404 link:

  ...
  import * as React from 'react';
+ import { Link } from 'react-router-dom';

  ...
  ...

  // Component
  const Home = () => (
    <Document>
      ...
      <Body>
        <Layout>
          ...
          <div styleName="footnote">
            <div styleName="button-wrapper">
              <ExLink>View on GitHub</ExLink>
+             <Link className="btn btn-secondary btn-sm" to="/404">
+               404
+             </Link>
            </div>
          </div>
        </Layout>
      </Body>
    </Document>
  );

  ...
  ...

commit: Add navigation link to 404 screen

Back to top

Adding API Services

Setting up environment variables

The only one thing we need to do is to define a placeholder to the API’s base URL.

For development, add the following lines to .env.development file:

+ # API
+ REACT_APP_API_URL=null
+
  # Web application
  REACT_APP_WEB_URL=http://localhost:3000

commit: Define API's base URL placeholder in development environment

For production, add the following lines to .env.production file:

+ # API
+ REACT_APP_API_URL=null
+
  # Web application
  REACT_APP_WEB_URL=https://setup-react-app.herokuapp.com

commit: Define API's base URL placeholder in production environment

Adding an API configuration

From the project’s root directory, run the following command to create a configuration file:

touch src/config/api.js

Then, add the configuration below to the newly created file:

export default {
  url: process.env.REACT_APP_API_URL,
  endpoints: {}
};

commit: Add API configuration

Installing Promise based HTTP library

Introducing Axios

Axios is a very popular Promise based HTTP library for JavaScript that runs on both the client and the server. With Axios it’s easy to make an asynchronous HTTP request to RESTful API endpoints and perform CRUD operations.

On the command line, let’s install Axios with these commands:

yarn add axios

commit: Install axios package

yarn type:install

commit: Update library definitions

Back to top

Adding the App Skeleton

We will construct the app skeleton by combining core components together and wrap the entire structure with Router component. The Router will use the HTML5 history API to keep our UI in sync with the URL.

To do that, open src/components/core/App/index.jsx file and update with the content below:

  // @flow
  // Module dependencies
  import * as React from 'react';
  import { hot } from 'react-hot-loader';
+ import { BrowserRouter as Router } from 'react-router-dom';
+ import { LastLocationProvider as Location } from 'react-router-last-location';
+ import ScrollMemory from 'react-router-scroll-memory';
+
+ import Content from '../Content';
+ import Footer from '../Footer';
+ import Header from '../Header';
+ import Main from '../Main';
+ import Routes from '../Routes';
+ import Wrapper from '../Wrapper';

  // Component
- const App = () => <div>App component</div>;
+ const App = () => (
+   <Router>
+     <React.Fragment>
+       <ScrollMemory />
+       <Location>
+         <Wrapper>
+           <Header />
+           <Content>
+             <Main>
+               <Routes />
+             </Main>
+             <Footer />
+           </Content>
+         </Wrapper>
+       </Location>
+     </React.Fragment>
+   </Router>
+ );

  // Module exports
  export default hot(module)(App);

commit: Add app skeleton

Back to top

Analyzing the Bundle Size

Source Map Explorer analyzes JavaScript (or Sass) bundles using the source maps. It determines which file each byte in your build came from. It shows you a treemap visualization to help you understand where code bloat is coming from.

To add Source Map Explorer to you project, follow these steps:

Installation

yarn add source-map-explorer

commit: Install source-map-explorer package

yarn type:install

commit: Update library definitions

Configuration

In package.json file, add the following line to scripts section:

  {
    "scripts": {
+     "analyze": "source-map-explorer build/static/js/main.*",
      "build": "node scripts/build.js",
      ...
    }
  }

commit: Add script for analyzing the bundle size

Usage

To analyze the bundle size, run the production build command:

yarn build

And run the analyze script:

yarn analyze

Then, Source Map Explorer will launch your default browser automatically with the result of your bundle.

Back to top

Code Splitting

Problem

So you’ve got your React app, you’re bundling it with Create React App (using Webpack obviously) or Buildpacks (with Heroku for instance), and its performance are going smooth. But then one day you notice your bundle (package) is getting so big that it’s slowing performance down.

Bundling is great, but as your app grows, your bundle size will grow too. Especially if you are including large third-party libraries. You need to keep an eye on the code you are including in your bundle so that you don’t accidentally make it so large that your app takes a long time to load.

Solution

To avoid winding up with a large bundle, it’s time to start code-splitting your app! Code-Splitting is a feature supported by module bundlers like Webpack and Browserify which can take one large bundle containing your entire app, and splitting them up into multiple smaller bundles (chunks) which contain separate parts of your app.

Code-splitting your app can help you “lazy-load” just the things that are currently needed by the user, which can dramatically improve the performance of your app. You’ve avoided loading code that the user may never need and reduced the amount of code needed during the initial load.

The best way to introduce code-splitting into your app is through the dynamic ES6 import() syntax.

Installation

We will be using a small library called react-loadable to make our life easier. React Loadable is a higher order component for loading components with dynamic imports. It wraps dynamic imports in a nice, React-friendly API for introducing code-splitting into your app at a given component.

Let’s install the library:

yarn add react-loadable

commit: Install react-loadable package

We also need to install react-loadable-visibility library, a wrapper around React Loadable to load elements that are visible on the page:

yarn add react-loadable-visibility

commit: Install react-loadable-visibility package

yarn type:install

commit: Update library definitions

Creating a great "Loading..." component

Rendering just a static "Loading..." message doesn’t communicate enough information to the user. We also need to think about errors, timeouts..., and making it a nice experience.

Helper function to work with window object

First, let’s create a helper function to work with window object by opening src/helpers/utilities.js file and add the following function:

  // @flow

  // Deley
- // eslint-disable-next-line
  export const delay = (callback: Function, duration: number) => {
    setTimeout(() => {
      callback();
    }, duration);
  };

+ // Reload webpage
+ export const reload = () => {
+   window.location.reload();
+ };

commit: Add helper function to reload webpage

Loader component

On the command line, create Loader folder inside src/components/common:

mkdir src/components/common/Loader

Then, create a component along with its test file and stylesheet inside Loader:

cd src/components/common/Loader
touch index.jsx index.test.js styles.scss

To create a component, add the content below to index.jsx file:

// @flow
// Module dependencies
import * as React from 'react';

import Layout from 'components/common/Layout';
import Spinner from 'components/common/Spinner';
import { reload } from 'helpers/utilities';

// Styles
import './styles.scss';

// Types
type Props = {
  error: any,
  pastDelay: boolean,
  timedOut: boolean
};
type Return = React.Node;

// Wrapper
const Wrapper = ({ children }: { children: React.Node }): Return => (
  <Layout size="col-md-8 col-lg-6">{children}</Layout>
);

// Component
const Loader = (props: Props): Return => {
  // When the loader has errored
  if (props.error) {
    return (
      <Wrapper>
        <div className="card">
          <div className="card-header">Sorry</div>
          <div className="card-body">
            <p className="card-text message">Something went wrong, please reload a webpage.</p>
            <button className="btn btn-primary" onClick={() => reload()}>
              Reload
            </button>
          </div>
        </div>
      </Wrapper>
    );
  }

  // When the loader has taken longer than the timeout
  if (props.timedOut) {
    return (
      <Wrapper>
        <Spinner />
        <div styleName="timeout">
          <p className="message">Please take a moment</p>
        </div>
      </Wrapper>
    );
  }

  // When the loader has taken longer than the delay
  if (props.pastDelay) {
    return (
      <Wrapper>
        <Spinner />
      </Wrapper>
    );
  }

  // When the loader has just started
  return null;
};

// Module exports
export default Loader;
export { Wrapper };

You may need to add tests to index.test.js file:

// Module dependencies
import { shallow } from 'enzyme';
import React from 'react';

// Components
import Spinner from 'components/common/Spinner';
import Loader, { Wrapper } from './index';

// Tests
describe('components/common/Loader', () => {
  it('should render nothing', () => {
    // Shallow rendering
    const wrapper = shallow(<Loader />);

    // Assertions
    expect(wrapper.get(0)).toBeNull();
  });

  it('should accept "error" prop', () => {
    // Mock data
    const data = true;
    const message = 'Something went wrong, please reload a webpage.';

    // Shallow rendering
    const wrapper = shallow(<Loader error={data} />);

    // Assertions
    expect(wrapper.find('.message').text()).toEqual(message);
  });

  it('should accept "timedOut" prop', () => {
    // Mock data
    const data = true;
    const message = 'Please take a moment';

    // Shallow rendering
    const wrapper = shallow(<Loader timedOut={data} />);

    // Assertions
    expect(wrapper.find(Spinner)).toHaveLength(1);
    expect(wrapper.find('.message').text()).toEqual(message);
  });

  it('should accept "pastDelay" prop', () => {
    // Mock data
    const data = true;

    // Shallow rendering
    const wrapper = shallow(<Loader pastDelay={data} />);

    // Assertions
    expect(wrapper.find(Spinner)).toHaveLength(1);
  });

  it('should reload a web page when a button is clicked', () => {
    // Mock data and functions
    const data = true;
    window.location.reload = jest.fn();

    // Shallow rendering
    const wrapper = shallow(<Loader error={data} />);

    // Simulate user interaction
    wrapper
      .find('button')
      .at(0)
      .simulate('click');

    // Assertions
    expect(window.location.reload).toHaveBeenCalled();
  });
});

describe('components/common/Loader.Wrapper', () => {
  it('should render children', () => {
    // Mock data
    const children = <div>Content</div>;

    // Shallow rendering
    const wrapper = shallow(<Wrapper>{children}</Wrapper>);

    // Assertions
    expect(wrapper).toContainReact(children);
  });
});

Let’s add a minimal amount of styles to the component as below:

// Global styles
@import '../../../styles/base/settings';

.timeout {
  color: #6c757d;
  font-size: 0.85rem;
  padding-top: 1rem;
}

commit: Create Loader component

With loadable HOC

On the command line, create withLoadable folder inside src/HOCs/common:

mkdir src/HOCs/common/withLoadable

Then, create a component along with its test file inside Loader:

cd src/HOCs/common/withLoadable
touch index.jsx index.test.js

To create a component, add the content below to index.jsx file:

// @flow
// Module dependencies
import * as React from 'react';
import LoadableVisibility from 'react-loadable-visibility/react-loadable';

import Loader from 'components/common/Loader';

// Types
type Props = Function;
type Return = React.Node;

// Component
const withLoadable = (loader: Props): Return =>
  LoadableVisibility({
    delay: 200,
    loader,
    loading: Loader,
    timeout: 10000
  });

// Module exports
export default withLoadable;

You may need to add tests to index.test.js file, the following is a simple “smoke test” verifying that a component renders without throwing:

// Module dependencies
import { shallow } from 'enzyme';
import React from 'react';

// Components
import withLoadable from './index';

// Tests
describe('HOCs/common/withLoadable', () => {
  it('should render without crashing', () => {
    shallow(<withLoadable />);
  });
});

commit: Create withLoadable HOC

Route-based code splitting

A good place to introduce code-splitting is to break your app into separate routes and load each one asynchronously because most users are used to page transitions taking some amount of time to load.

Open src/components/core/Routes/index.jsx file and update with the following changes:

+ import withLoadable from 'HOCs/common/withLoadable';

  ...

  // Screens
- import Home from 'screens/main/Home';
- import NotFound from 'screens/main/404';
+ const Home = withLoadable(() => import('screens/main/Home'));
+ const NotFound = withLoadable(() => import('screens/main/404'));

commit: Refactor Routes component with route-based code-splitting

Back to top

Deployment

With your app all tested up through this point, it’s time to get it up and live for the world to see. Now, comes a time when you need to deploy your application on a testing or production server and ask yourself “how?”.

Choosing the right server

First thing you need to figure out is what kind of server environment will you need for hosting your app.

Our React & Redux app will be just a static website like we used to do back in 1990 before PHP took the world by a storm. Our deployment process will at the very basic level be “put the files on the server”. There are some edge cases which we will cover later on.

Introducing Heroko

Heroku is a container-based cloud Platform as a Service (PaaS). The platform is elegant, flexible, and easy to use, offering developers the simplest path to getting their apps to market.

This section will demonstrate how to deploy your React app to Heroku using Heroku Git.

Setting up deployment tools

Heroku CLI

Before you get started, you must have Heroku CLI installed on your machine.

Heroku CLI makes it easy to create and manage your Heroku apps directly from the terminal. It’s an essential part of using Heroku. To install it, follow the Heroku CLI installation instructions.

To verify your CLI installation, use the command below:

heroku --version

You should see something like heroku/7.0.17 darwin-x64 node-v10.0.0 in the output.

After the installation, run the following command to log in with your Heroku account credentials:

heroku login
Enter your Heroku credentials.
Email: me@mail.com
Password (typing will be hidden):
Authentication successful.

The CLI saves your email address and an API token to your ~/.netrc directory for future use. For more information, see Heroku CLI Authentication.

Heroku Repo plugin

You may need to install a plugin for Heroku CLI that can manipulate the repo. This plugin adds some commands to the Heroku CLI to interact with the app’s repo.

On the command line, run the following command to install the plugin:

heroku plugins:install heroku-repo

Now you’re good to go!

Creating a Heroku remote

Git remotes are versions of your repository that live on other servers. You deploy your app by pushing its code to a special Heroku-hosted remote that’s associated with your app.

For a new Heroku app

The heroku create command creates a new empty application on Heroku, along with an associated empty Git repository.

On the command line, run the following command in your project’s root directory:

heroku create setup-react-app

If you run this command in your project’s root directory, the empty Heroku Git repository is automatically set as a remote for your local repository.

To verify that a remote repository named heroku is set for your project successfully, run a command:

git remote -v

The output should look like this:

heroku  https://git.heroku.com/setup-react-app.git (fetch)
heroku  https://git.heroku.com/setup-react-app.git (push)
origin  https://rxseven@github.com/rxseven/setup-react-app.git (fetch)
origin  https://rxseven@github.com/rxseven/setup-react-app.git (push)

Note: the origin is hosted on GitHub.

For an existing Heroku app

If you have already created your Heroku app, you can easily add a remote to your local repository with the following command. All you need is your Heroku app’s name:

heroku git:remote -a setup-react-app

Deploying React & Redux app

Introducing buildpacks

Buildpacks are scripts that are run when your app is deployed to Heroku. They are used to install dependencies for your app and configure your environment.

Setting a buildpack on an application

We will be using Heroku Buildpack for CRA to transform our deployed React code into a slug, which can then be executed on a dyno.

You can change the buildpack used by an application by setting the buildpack value. When the application is next pushed, the new buildpack will be used. To set the buildpack value to the existing app, run the following command in the project’s root directory:

heroku buildpacks:set https://github.com/mars/create-react-app-buildpack.git

Note: you can manage buildpacks used by your app in Heroku’s project settings page

Configuring client-side routing

This section will demonstrate how to fix the client-side routing errors (404) when running a Single Page Application (SPA) on Heroku. For more information, see this discussion.

In the project’s root directory, run the following command to create a configuration file:

touch static.json

Then, add the configuration below to the newly created file:

{
  "clean_urls": false,
  "root": "build/",
  "routes": {
    "/**": "index.html"
  }
}

commit: Setup client-side routing

Building for relative paths

By default, Create React App produces a build assuming your app is hosted at the server root.

Specifying the homepage field in package.json will let Create React App correctly infer the root path to use in the generated HTML file.

Since our app will be hosting at the root path on Heroku, the homepage field must not be defined.

This commit was a mistake, it included homepage field which we don’t need (as mentioned above). To fix this, remove the following line from package.json file:

  {
    ...
    "version": "0.1.0",
-   "homepage": "https://github.com/rxseven/setup-react-app",
    "license": "MIT",
    ...
  }

commit: Remove homepage field from package.json

Deploying code

To deploy your app to Heroku, you typically use the git push command to push the code from your local repository’s master branch to your Heroku remote:

git push heroku master

Use this same command whenever you want to deploy the latest committed version of your code to Heroku.

Note: Heroku only deploys code that you push to the master branch of the heroku remote. Pushing code to another branch of the remote has no effect.

Deploying from a branch besides master

If you want to deploy code to Heroku from a non-master branch of your local repository (for example, release-0.1.0), use the following syntax to ensure it is pushed to the remote’s master branch:

git push heroku release-0.1.0:master

Resetting a remote repository

To reset a remote repository, run the following command:

heroku repo:reset -a setup-react-app

Caution this command will empty the remote repository, please use it carefully.

Back to top

Continuous Integration

Continuous Integration (CI) is a practice where a team of developers merge their code early and often to the mainline or code repository. The main goal is to reduce the risk of integration problems, referred to as “integration hell” by waiting for the end of a sprint or a project to merge the work of all developers.

One of the primary benefits of adopting continuous integration is that it will save your time during your development cycle by identifying and addressing conflicts early. It’s also a great way to reduce the amount of time spent on fixing conflicts, bugs, and regression by putting more emphasis on having a good test suite. Last but not least, it helps you share a better understanding of the codebase and the features that you’re developing for your users.

Setup

In the following section will demonstrate how to setup continuous integration with Travis CI.

Configuration

On the command line, create a configuration file in the project’s root directory:

touch .travis.yml

Then, add the following configuration to the newly created file:

language: node_js
node_js:
  - 6
cache:
  directories:
    - node_modules

commit: Setup Travis CI

Running automated tasks

The first step on your journey to continuous integration is setting up automated tasks, let’s add the following scripts to run your tasks automatically:

  language: node_js
  node_js:
    - 6
  cache:
    directories:
      - node_modules
+ script:
+   - npm run lint:script
+   - npm run lint:stylesheet
+   - npm run type:check:focus
+   - npm run test:coverage
+   - npm run build

commit: Setup automated tasks

Adding Travis CI’s build status badge

You can embed status images (also known as badges or icons) that show the status of your build into your README file. For more information on adding build status badge, see Embedding Status Images on Travis CI documentation.

  1. In Travis CI’s project dashboard, find a status icon next to the project title.
  2. Click the icon to open a dialog box containing common templates for the status image URL.
  3. Select the master branch and Markdown template in the dialog box.
  4. Copy the embedded code snippet, and paste it to your README.md file:
  # Setup React App
+
+ [![Build Status](URL)

You should now be able to view the build status badge for your repository is publicly available on Travis CI, and your badge should look like this:

Build Status

You have set up continuous integration for your project which informs you when your tasks fails. Furthermore, it shows a fancy badge in your repository to inform other people that your project builds successfully. It adds credibility to the project.

commit: Add Travis CI's build status badge to README

Adding code coverage to find untested code

Once you adopt automated testing, it is a good idea to couple it with a test coverage tool that will give you an idea of how much of your codebase is covered by your test suite.

We will be using Coveralls with Travis CI. Coveralls takes the build data from whichever CI service your project uses, parses it, and provides constant updates and statistics on your project’s code coverage to show you how coverage has changed with the new build, and what isn’t covered by tests.

Installation

First, let’s install Coveralls library on the command line as development dependency:

yarn add --dev coveralls

commit: Install coveralls package

Connecting Travis CI to Coveralls

Coveralls

  1. Open your Coveralls’s repo settings page by clicking Settings menu at the right-top corner of the screen.
  2. Under Badge and Token section, copy Repo Token to a clipboard.

Travis CI

  1. Open your Travis CI’s project settings page by clicking More options button (right next to the tabbed navigation).
  2. Under Environment Variables section, add new environment variable for referencing your Coveralls’s Repo token:
  • Name: COVERALLS_REPO_TOKEN
  • Value: REPO_KEY (the key you’ve copied from your Coveralls repo)
  • Displaying value in build log: disabled
  1. Click Add button to save the newly created variable.

Best practices for securely storing API keys
Storing API Keys, or any other sensitive information, on a Git repository is something to be avoided at all costs. Even if the repository is private, you should not see it as a safe place to store sensitive information. (details are available in this article)

Setting up code coverage

Add a new script to package.json file to introduce Coveralls as the following:

  {
    "scripts": {
      ...
      "test:coverage": "yarn test --coverage",
+     "test:coverage:ci": "cat ./coverage/lcov.info | node node_modules/.bin/coveralls",
      "test:staged": "cross-env CI=true node scripts/test.js --env=jsdom --config jest.config.json --findRelatedTests"
      ...
    }
  }

Then, extend the Travis CI’s configuration for reporting test coverage information to your Coveralls’s dashboard. To do this, add the following lines to .travis.yml file:

  ...
  script:
    ...
    ...
+ after_script:
+   - COVERALLS_REPO_TOKEN=$COVERALLS_REPO_TOKEN npm run test:coverage:ci

For more information on configuring your Travis CI build to send results to Coveralls, see this documentation.

commit: Setup Coveralls

Adding Coveralls’s coverage status badge

  1. In Coveralls’s repo settings page, find EMBED button under Badge and Token section.
  2. Click the button to open a dialog box containing common templates for the status image URL.
  3. Copy the embedded code snippet from Markdown template, and paste it next to Travis CI’s status badge in README.md file:
  # Setup React App

- [![Build Status](URL)
+ [![Build Status](URL) [![Coverage Status](URL)

You should now be able to view the coverage status badge for your repository is publicly available on Coveralls, and your badge should look like this:

Coverage Status

That’s it.

commit: Add Coveralls's coverage status badge to README

It is good to aim a coverage above 80% but be careful not to confuse high percentage of coverage with a good test suite. A code coverage tool will help you find untested code but it is the quality of your tests that will make the difference at the end of the day.

Back to top

Continuous Deployment

Continuous deployment (CD) is an excellent way for teams to accelerate development. It removes the impediments related to the release approval process by making it an automated process, and it allows developers to get feedback from users as soon as they’re completed with their work. Issues are easier to identify and fix, and there’s less context switching as there’s no release time anymore.

You’ll see in this section how you can implement a continuous deployment using Travis CI to automatically deploy your React app to Heroku after a successful build.

Connecting Travis CI to Heroku

Heroku

  1. Open your Heroku account page by clicking on your account avatar at the right-top corner of the screen.
  2. Under API Key section, click Reveal button to show your secret API key.
  3. Once the key shows up, copy it to a clipboard, and click Hide button to hide the key.

Travis CI

  1. Open your Travis CI’s project settings page by clicking on More options button (right next to the tabbed navigation).
  2. Under Environment Variables section, add new environment variable for referencing your Heroku’s API key:
  • Name: HEROKU_API_KEY
  • Value: API_KEY (the key you’ve copied from your Heroku account)
  • Displaying value in build log: disabled
  1. Click Add button to save the newly created variable.

Best practices for securely storing API keys
Storing API Keys, or any other sensitive information, on a Git repository is something to be avoided at all costs. Even if the repository is private, you should not see it as a safe place to store sensitive information. (details are available in this article)

Setting up continuous deployment

Open .travis.yml file and add the following lines to the bottom of the existing configuration:

deploy:
  provider: heroku
  app:
    develop: setup-react-app-dev
    master: setup-react-app
    staging: setup-react-app-staging
  api_key:
    secure: $HEROKU_API_KEY

For more information on setting up continuous deployment, see this documentation.

commit: Setup continuous deployment with Heroku & Travis CI

Back to top

Changelog

See releases.

Acknowledgements

This project is developed and maintained by Theerawat Pongsupawat, frontend developer from Chiang Mai, Thailand.

Credits

This project was bootstrapped with Create React App.

License

Setup React App is open source software licensed as MIT.

You can’t perform that action at this time.