Setup React App
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
- Adding Pre-commit Hooks
- Adding React Hot Loader
- Running JavaScript Linting
- Running Stylesheet Linting
- Running Static Type Checking
- Running Tests
- Formatting Code Automatically
- Using JSX Control Statements
- Using Absolute Imports
- Using HTTPS in Development
- Debugging in the Editor
- Debugging Tests
Ecosystem
- Adding Sass and Post-Processing CSS
- Adding CSS Modules
- Adding Redux
- Adding React Router
- Adding Bootstrap
- Adding Open Iconic
Boilerplate & Skeleton
- Adding a Global Stylesheet
- Adding Sass Boilerplate
- Adding a Layout Boilerplate
- Adding Common Components
- Adding Core Components
- Adding Main Screens
- Adding API Services
- Adding the App 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.
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.
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.
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.
Quick Start
In your working directory, clone Setup React App from GitHub:
git clone https://github.com/rxseven/setup-react-app.gitAnd run the following commands to get the app up and running:
cd setup-react-app
nvm use
yarn install
yarn startThen open http://localhost:3000 to view the app in your browser.
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-appThen, change the current working directory to your local project:
cd setup-react-appSetting 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 initThen, 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.emailThen, 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 .gitignorecommit: 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.mdThen, add the content below:
# Setup React Appcommit: 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.gitTo verify that a remote repository named origin is set for your project successfully, run the command below:
git remote -vThen, 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 masterSetting 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 developOnce a new branch is created, you should publish it to a remote repository:
git push -u origin developCreating a feature branch
When starting work on a new feature, branch off from the develop branch:
git checkout -b feature/setup-projectOnce a new branch is created, you should publish it to a remote repository:
git push -u origin feature/setup-projectSpecifying 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 .nvmrcAnd specify a Node version number to .nvmrc file:
echo "8.9.3" > .nvmrcNow, your .nvmrc file should contain a Node version number like 8.9.3.
Note: the contents of a
.nvmrcfile must be the<version>(as described bynvm --help) followed by a newline. No trailing spaces are allowed, and the trailing newline is required.
Usage
From the project’s root directory, run a simple command:
nvm useThen, 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)
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 .vscodeThen, create a configuration file inside that folder:
touch .vscode/settings.jsonFor 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:
- Open Command Palette in Visial Studio Code by pressing command + p.
- Type
ext install dzannotti.vscode-babel-coloringand hit enter. - 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:
- In Visual Studio Code, open a Settings screen by pressing command + ,.
- Select Workspace Settings tab.
- 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
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-appNote: 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-appcommit: 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 ejectThis 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.jsAlso 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.
Then, we need to update Yarn lockfile:
yarn installcommit: 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 .babelrcOpen 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.cssIn the project’s root directory, run the command below:
cd src && rm App.css App.js App.test.js logo.svg index.cssSince 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
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",
...
...
}Important note on
homepagefield inpackage.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",
...
...
}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.
Specifying the app title
Open public/index.html file and update <title> element:
- <title>React App</title>
+ <title>Setup React App</title>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.jsxThen, 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": {
...
}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/AppThen, create a component file inside App:
touch index.jsxTo 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.jsThen, 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.jsInside 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.jsThen, 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.productionSpecify app URLs
Open .env.development file and add the content below:
# Web application
REACT_APP_WEB_URL=http://localhost:3000
Open .env.production file and add the content below:
# Web application
REACT_APP_WEB_URL=https://setup-react-app.herokuapp.com
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-envcommit: 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 lodashcommit: 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 ramdacommit: Install ramda package
Classnames - A simple JavaScript utility for conditionally joining CSS class names together.
yarn add classnamescommit: Install classnames package
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 huskycommit: Install husky package
Follow by Lint-staged:
yarn add --dev lint-stagedcommit: Install lint-staged package
Configuration
Lint-staged
In the project’s root directory, let’s create a configuration file:
touch .lintstagedrcOnce 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
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-loadercommit: 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
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 .eslintrccommit: 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 .eslintignorecommit: 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-airbnbTo list its peer dependencies and their versions, run the command below:
npm info "eslint-config-airbnb@latest" peerDependenciesThen, 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-a11yThen, install the latest version as a development dependency:
yarn add --dev eslint-plugin-jsx-a11yConfiguration
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
pluginssection in.eslintrcfile since they are already pre-configured in the shareable configuration.
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
- Open Command Palette in Visial Studio Code by pressing command + p.
- Type
ext install vscode-eslintand hit enter. - 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:scriptTo automatically fix problems, run the following script instead:
yarn lint:script:fixWith :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:fixscript defined beforehand.
commit: Add pre-commit hook for running code linting against staged files
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 stylelintcommit: 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-scsscommit: 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 .stylelintrcOnce 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-scssYou may also need another shareable configuration to tweak Stylelint rules to accept CSS modules specific syntax:
yarn add --dev stylelint-config-css-modulesConfiguration
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
}
}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
- Open Command Palette in Visial Studio Code by pressing command + p.
- Type
ext install stylelintand hit enter. - 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,
}
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:stylesheetPreventing 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:stylesheetscript defined beforehand.
commit: Add pre-commit hook for running stylesheet linting against staged files
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-binNote: 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 initThe 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=.scssLibrary 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-typedcommit: 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"
}
}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:installcommit: 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
- Open Command Palette in Visial Studio Code by pressing command + p.
- Type
ext install flowtype.flow-for-vscodeand hit enter. - 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.1and higher, please do double check your current version inpackage.jsonfile. 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-eslintThen, install a new version:
yarn add --dev babel-eslint@8.2.3Running 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:checkPreventing 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
...
...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);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.jsonOpen 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 enzymecommit: Install enzyme package
and:
yarn add --dev enzyme-adapter-react-16Each 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-rendererAdditionally, 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-enzymecommit: 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.jsThen, 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-storecommit: 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-jestConfiguration
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
- Open Command Palette in Visial Studio Code by pressing command + p.
- Type
ext install orta.vscode-jestand hit enter. - 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
testscript 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 testBy 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:coverageNote: 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.jsThen, 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
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 prettiercommit: Install prettier package
Configuration
In the project’s root directory, create a configuration file:
touch .prettierrccommit: 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 .prettierignorecommit: 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
- Open Command Palette in Visial Studio Code by pressing command + p.
- Type
ext install prettier-vscodeand hit enter. - 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
+
...
...
}
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.jsonthis only runs Prettier since ESLint cannot process those.
Installation
We need to install this library as a development dependency:
yarn add --dev prettier-eslintcommit: 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
}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-cliConfiguration
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 formatPreventing 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 formatscript defined beforehand.
commit: Add pre-commit hook for running code formatting against staged files
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-statementsConfiguration
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-statementscommit: 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
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
.envfile 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
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 startNote: 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",
...
}Usage
Run yarn start:https on the command line and view your app in the browser at https://localhost:3000.
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:
- Open Command Palette in Visial Studio Code by pressing command + p.
- Type
ext install debugger-for-chromeand hit enter. - 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.jsonThen, 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"
}Usage
- Start your app by running
yarn startoryarn start:httpscommand. - Open Debug panel in Visual Studio Code, select Debug JavaScript from a drop-down menu.
- Start debugging by pressing F5 or by clicking the Start Debugging button next to the drop-down menu.
- You can then write code as usual, set breakpoints, make changes to the code, and debug your newly modified code, all from your editor.
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": "...",
...
}
}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
- Start your app by running
yarn startoryarn start:httpscommand. - Open Debug panel in Visual Studio Code, select Debug Tests from a drop-down menu.
- Start debugging by pressing F5 or by clicking the Start Debugging button next to the drop-down menu.
- You can then write code as usual, set breakpoints, make changes to the code, and debug your newly modified code, all from your editor.
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-sasscommit: Install node-sass package
We will also need to install Sass loader for Webpack:
yarn add --dev sass-loadercommit: 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')
}
]
},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
)
)
},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',
})
]
}
},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'
})
]
}
},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
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]'
}
}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]'
}
}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
camelCaseCSS class names. - You have to use
stylesobject whenever constructing aclassName. - Mixing CSS Modules and global CSS classes is cumbersome.
- Reference to an
undefinedCSS Module resolves toundefinedwithout 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
camelCasenaming convention. - You do not need to refer to the
stylesobject 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-modulesThe 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-scsscommit: 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"]
}Adding Redux
Installing Redux ecosystem
Redux is a very popular library that provides a predictable state container for JavaScript applications.
yarn add reduxcommit: Install redux package
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-reduxcommit: Install react-redux package
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-sagacommit: Install redux-saga package
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 reselectcommit: Install reselect package
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 immutablecommit: Install immutable package
Redux utility for creating an equivalent function of Redux combineReducers that works with Immutable.js state.
yarn add redux-immutablecommit: Install redux-immutable package
A Higher order component decorator for forms using React and Redux to keep form state in a Redux store.
yarn add redux-formcommit: Install redux-form package
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 normalizrcommit: Install normalizr package
Library Definitions
Finally, don’t forget to update the library definitions:
yarn type:installcommit: 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/RootNow, 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.jsThen, 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.jsThen, 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.jsThen, 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.jsComponent
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 />);
+ });
});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.jsxComponent
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.jsReducer
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();
});
});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.jsReducer
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();
});
});Creating state models
On the command line, create models folder inside src/constants:
mkdir src/constants/modelsThen, create state.js file inside models folder:
touch src/constants/models/state.jsInside 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 }
}
}
})
}
};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
To configure client-side routing, we will be using React Router, the most popular declarative routing for React.
yarn add react-router-domcommit: Install react-router-dom package
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-locationA 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-memoryLibrary Definitions
Finally, don’t forget to update the library definitions:
yarn type:installcommit: Update library definitions
Defining paths variable for router
Create paths.js file inside src/constants/router folder:
touch src/constants/router/paths.jsThen, 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/RoutesThen, create a component along with its test file inside Routes:
cd src/components/core/Routes && touch index.jsx index.test.jsTo 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
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 bootstrapcommit: Install bootstrap package
yarn type:installcommit: 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
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-iconiccommit: Install open-iconic package
yarn type:installcommit: 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
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 pluginsNote: you may need to add placeholder styles to
index.scssand_settings.scssfiles 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
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';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.scssThen, 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.scssThen, 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.scssThen, 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.scssThen, 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.scssThen, 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
Adding a Layout Boilerplate
On the command line, create _layout.scss file inside src/styles/base:
touch src/styles/base/_layout.scssThen, 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';Adding Common Components
Grid
On the command line, create Grid folder inside src/components/common:
mkdir src/components/common/GridThen, create an index file inside Grid:
touch src/components/common/Grid/index.jscommit: Create Grid index
Container
Create Container folder inside Grid:
mkdir src/components/common/Grid/ContainerThen, create a component along with its test file inside Container:
cd src/components/common/Grid/Container
touch index.jsx index.test.jsComponent
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/RowThen, create a component along with its test file inside Row:
cd src/components/common/Grid/Row
touch index.jsx index.test.jsComponent
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/ColumnThen, create a component along with its test file inside Column:
cd src/components/common/Grid/Column
touch index.jsx index.test.jsComponent
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/LayoutThen, create a component along with its test file inside Layout:
cd src/components/common/Layout
touch index.jsx index.test.jsComponent
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/PageThen, create an index file inside Page:
touch src/components/common/Page/index.jscommit: Create Page index
Document
Create Document folder inside Page:
mkdir src/components/common/Page/DocumentThen, create a component along with its test file inside Document:
cd src/components/common/Page/Document
touch index.jsx index.test.jsComponent
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/BodyThen, create a component along with its test file inside Body:
cd src/components/common/Page/Body
touch index.jsx index.test.jsComponent
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/HeadThen, create a component along with its test file inside Head:
cd src/components/common/Page/Head
touch index.jsx index.test.jsComponent
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/TitleThen, create a component along with its test file inside Title:
cd src/components/common/Page/Title
touch index.jsx index.test.jsDependencies
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-helmetcommit : Install react-helmet package
yarn type:installcommit: 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/SpinnerThen, create a component along with its test file and stylesheets inside Spinner:
cd src/components/common/Spinner
touch index.jsx index.test.js styles.scssComponent
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/ExLinkThen, create a component along with its test file inside ExLink:
cd src/components/common/ExLink
touch index.jsx index.test.jsComponent
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)
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/WrapperThen, create a component along with its test file and stylesheets inside Wrapper:
cd src/components/core/Wrapper
touch index.jsx index.test.js styles.scssTemporary 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.jsThen, 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/ContentThen, create a component along with its test file inside Content:
cd src/components/core/Content
touch index.jsx index.test.jsBefore 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/HeaderThen, create a component along with its test file and stylesheets inside Header:
cd src/components/core/Header
touch index.jsx index.test.js styles.scssComponent
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/MainThen, create a component along with its test file and stylesheets inside Main:
cd src/components/core/Main
touch index.jsx index.test.js styles.scssComponent
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/FooterThen, create a component along with its test file and stylesheets inside Footer:
cd src/components/core/Footer
touch index.jsx index.test.js styles.scssComponent
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 & built with all the love in{' '}
<ExLink to="https://reactjs.org">React</ExLink> &{' '}
<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
Adding Main Screens
Home page
On the command line, create Home folder inside src/screens/main:
mkdir src/screens/main/HomeThen, create a component along with its test file and stylesheets inside Home:
cd src/screens/main/Home
touch index.jsx index.test.js styles.scssComponent
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 & 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;
+ }
+ }
+ }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/404Then, create a component along with its test file inside 404:
cd src/screens/main/404
touch index.jsx index.test.jsComponent
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>
);
...
...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:3000commit: 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.comcommit: 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.jsThen, 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 axioscommit: Install axios package
yarn type:installcommit: Update library definitions
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
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-exploreryarn type:installcommit: 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",
...
}
}Usage
To analyze the bundle size, run the production build command:
yarn buildAnd run the analyze script:
yarn analyzeThen, Source Map Explorer will launch your default browser automatically with the result of your bundle.
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-loadablecommit: 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-visibilityyarn type:installcommit: 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();
+ };Loader component
On the command line, create Loader folder inside src/components/common:
mkdir src/components/common/LoaderThen, create a component along with its test file and stylesheet inside Loader:
cd src/components/common/Loader
touch index.jsx index.test.js styles.scssTo 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/withLoadableThen, create a component along with its test file inside Loader:
cd src/HOCs/common/withLoadable
touch index.jsx index.test.jsTo 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
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 --versionYou 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
~/.netrcdirectory 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-repoNow 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-appIf 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 -vThe 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-appDeploying 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.gitNote: 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.jsonThen, 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",
...
}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 masterUse 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:masterResetting a remote repository
To reset a remote repository, run the following command:
heroku repo:reset -a setup-react-appCaution this command will empty the remote repository, please use it carefully.
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.ymlThen, add the following configuration to the newly created file:
language: node_js
node_js:
- 6
cache:
directories:
- node_modulescommit: 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 buildcommit: 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.
- In Travis CI’s project dashboard, find a status icon next to the project title.
- Click the icon to open a dialog box containing common templates for the status image URL.
- Select the master branch and Markdown template in the dialog box.
- Copy the embedded code snippet, and paste it to your
README.mdfile:
# Setup React App
+
+ [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:
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.
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 coverallscommit: Install coveralls package
Connecting Travis CI to Coveralls
Coveralls
- Open your Coveralls’s repo settings page by clicking Settings menu at the right-top corner of the screen.
- Under Badge and Token section, copy Repo Token to a clipboard.
Travis CI
- Open your Travis CI’s project settings page by clicking More options button (right next to the tabbed navigation).
- 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
- 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:ciFor 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
- In Coveralls’s repo settings page, find EMBED button under Badge and Token section.
- Click the button to open a dialog box containing common templates for the status image URL.
- Copy the embedded code snippet from Markdown template, and paste it next to Travis CI’s status badge in
README.mdfile:
# Setup React App
- [
+ [ [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:
That’s it.
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.
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
- Open your Heroku account page by clicking on your account avatar at the right-top corner of the screen.
- Under API Key section, click Reveal button to show your secret API key.
- Once the key shows up, copy it to a clipboard, and click Hide button to hide the key.
Travis CI
- Open your Travis CI’s project settings page by clicking on More options button (right next to the tabbed navigation).
- 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
- 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_KEYFor more information on setting up continuous deployment, see this documentation.
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.