Adebiyi Adedotun Caught in the web, breaking things and learning fast.

Creating a React app toolchain from scratch

7 min read 1996

The React logo.

When starting a new React project, there are a bevy of options available. Each has its own merits and shortcomings that leave room for comparison.

With tools like create-react-app, Next, or Gatsby, you’re entering a territory of abstract, advanced, and highly opinionated toolchain, which undoubtedly comes with several benefits. However, there are times when You might not need a toolchain, and that even leaves you with a lot to desire.

One thing you need to understand is that you can use as little or as much React as you want on your website. What this means is that React can be applied to specific portions of your website where you feel the need for it. That has been covered in Adding React to a Website.

But what if you could set up a new React app toolchain for a weekend/pet project, with the absolutely necessary packages to begin with, and include more sophisticated packages as you go along?

You could build a toolchain that includes things like bundling, linting, formatting etc., without the overhead of abstract and opinionated tools or the learning curve. You could also learn so much about how things work behind the scenes and how to put little bits of the pieces together to create something substantial.

Getting started

We’ll be building a toolchain that includes:

  • Bundling (with Parcel)
  • Linting and Formatting (with ESLint and Prettier)
  • Transpiling (with Babel)
  • Styling (CSS/SCSS/Styled Components)
  • Data Fetching (with fetch)

Keep in mind that this is not an in-depth look into each of these tools. Instead, we’re just going to go over the necessary bits and pieces to get you up and running. That being said, let’s get to it.

Cloning the react-app-toolchain repo

To get started, you need to clone the repo here.

The folder contains the needed folders and files to get started with.

The starter folder looks like this:

We made a custom demo for .
No really. Click here to check it out.

- public
  - index.html
- src
  - components
    - Users.js
  - App.js
  - app.css
  - app.scss
- .eslintrc
- .prettierignore
- .prettierrc
- .gitignore
- package.json

Including React

Now that we have created the necessary folders, we can include the two necessary dependencies for React:

  • react
  • react-dom
yarn add react react-dom

This will update the package.json to include the added dependencies. The versions of the package might be different.

"dependencies": {
  "react": "^16.13.1",
  "react-dom": "^16.13.1"
}

While you can technically write some React here, it’d be of no use unless we add a bundler and render the app to the DOM.

You can check the include-react branch for the latest code.

Adding a Bundler

The bundler we’d be using is Parcel. Parcel is a zero-config, lightweight, and fast web application bundler:

yarn add parcel-bundler -D

At this point your package.json file (except for the dependencies version numbers) should look like:

{
  "name": "react-app-toolchain",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "react": "^16.13.1",
    "react-dom": "^16.13.1"
  },
  "devDependencies": {
    "parcel-bundler": "^1.12.4"
  }
}

You can now write some components and run the app with Parcel.

Update the public/index.html file to:

<!-- public/index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>React App Toolchain</title>
  </head>
  <body>
    <!-- We'd render the react app in here -->
    <div id="app"></div>
    <!-- We reference the entry point of our React app here -->
    <script src="../src/App.js"></script>
  </body>
</html>

And src/App.js to:

// src/App.js
import React from "react";
import ReactDOM from "react-dom";

function App() {
  return <h1>Hello World!</h1>;
}

ReactDOM.render(<App />, document.getElementById("app"));

Then add a start script to package.json that instructs parcel on the entry point of your app:

{
  "name": "react-app-toolchain",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "parcel public/index.html"
  },
  "dependencies": {
    "react": "^16.13.1",
    "react-dom": "^16.13.1"
  },
  "devDependencies": {
    "parcel-bundler": "^1.12.4"
  }
}

Now you can run the command yarn start and visit the running app at localhost:1234, or the port that Parcel runs the app on.

Text saying 'Hello world!'

You can check the include-bundler branch for the latest code.

Formatting

To format our files, we’ll use Prettier. Prettier is an opinionated code formatter that works with technologies like HTML, CSS, and JavaScript.

yarn add prettier -D

Then add two config files:

  • .prettierrc will contain the desired prettier rules, and
  • .prettierignore will contain the files/directories we do not need prettier to format, such as the node_modules

Add the following rules to the .prettierrc file:

{
  "arrowParens": "avoid",
  "bracketSpacing": true,
  "jsxBracketSameLine": false,
  "printWidth": 120,
  "proseWrap": "always",
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "all",
  "useTabs": false,
  "jsxSingleQuote": true
}

And to prettierignore:

node_modules
build
coverage
public
dist
.cache

You also need a command for formatting. Add another scripts to package.json — this time, format. This updates the package.json to:

{
  "name": "react-app-toolchain",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "parcel public/index.html",
    "format": "prettier \"src/**/*.js\" --write"
  },
  "dependencies": {
    "react": "^16.13.1",
    "react-dom": "^16.13.1"
  },
  "devDependencies": {
    "parcel-bundler": "^1.12.4",
    "prettier": "^2.0.5"
  }
}

Running the format script instructs Prettier to format all files ending with js in the src directory.

If you run the command yarn format, the double quotes in App.js should now be single quotes. Semi-colons should also be included where omitted, and the other rules in .prettierrc should be applied.

You’d have to keep running yarn format to format your files. If you’re using VSCode, you can install the Prettier extension, then turn on the editor.formatOnSave settings. When you save, Prettier will automatically format your file — even without the format script.

You can check the include-formatting branch for the latest code.

Linting

You’ve added those Prettier rules, but they’re not enforced. ESLint is a JavaScript linter that helps you find problems in your code.

yarn add eslint babel-eslint eslint-plugin-react-hooks eslint-config-prettier eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react -D

With the previous, command, you’ve not only added eslint, but also added packages like:

  • eslint-plugin-react-hooks
  • eslint-plugin-import
  • babel-eslint allows linting for all valid Babel code
  • eslint-config-prettier settles the conflict between ESLint and Prettier
  • eslint-plugin-jsx-a11y is a static AST checker for accessibility rules on JSX elements that helps with issues like not including a alt-text in images
  • eslint-plugin-react helps with react specific ESLint rules

Then update the .eslintrc file with the configs:

{
  "extends": [
    "eslint:recommended",
    "plugin:import/errors",
    "plugin:react/recommended",
    "plugin:jsx-a11y/recommended",
    "plugin:react-hooks/recommended",
    "prettier",
    "prettier/react"
  ],
  "plugins": ["react", "import", "jsx-a11y"],
  "parserOptions": {
    "ecmaVersion": 2018,
    "sourceType": "module",
    "ecmaFeatures": {
      "jsx": true
    }
  },
  "env": {
    "es6": true,
    "browser": true,
    "node": true
  },
  "rules": {
    "semi": ["warn", "always"],
    "quotes": ["warn", "single"]
  },
  "settings": {
    "react": {
      "version": "detect"
    }
  }
}

Here, rules instructs ESLint to give a warning when a semi-colon is omitted or when the single quote is not used. ecmaFeatures notifies ESLint that JSX has been used, and env contains the environments where the app will be run.

Now you need a lint script to help lint your code. The package.json scripts gets updated to:

"scripts": {
  "start": "parcel public/index.html",
  "format": "prettier \"src/**/*.js\" --write",
  "lint": "eslint \"src/**/*.{js,jsx}\""
},

Now you can run yarn lint to format all files in src that ends with .js or .jsx extension.

You’d have to keep running yarn lint to lint your files. If you’re using VSCode, you can install the ESlint extension. This will make sure you get error messages right in your file as you make them, and as ESlint detects them.

You can check the include-linting branch for the latest code.

Styling

There are many options to go with when it comes to styling. Let’s go over:

  • CSS
  • SCSS
  • Styled component

Adding a CSS file is straightforward. Update the app.css and app.scss files:

/* src/app.css */
.clr-red {
  color: red;
}
// src/app.scss
.bg-clr-black {
  background-color: black;
}

Also, add the necessary classes to the h1 in App.js and import both app.css and app.scss:

// src/App.js
import React from 'react';
import ReactDOM from 'react-dom';
import './app.css';
import './app.scss';

function App() {
  return <h1 className='clr-red bg-clr-black'>Hello World!</h1>;
}

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

Because you used sass, Parcel will automatically install and add sass to a list of the dependencies in package.json.

If you want to use a CSS-in-JS solution, like styled components, you’d have to install it.

yarn add styled-components -D
// src/App.js
import React from 'react';
import ReactDOM from 'react-dom';
import './app.css';
import './app.scss';
import styled from 'styled-components';

const StyledApp = styled.div`
  text-transform: uppercase;
`;

function App() {
  return (
    <StyledApp>
      <h1 className='clr-red bg-clr-black'>Hello World!</h1>
    </StyledApp>
  );
}

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

You can check the include-styling branch for the latest code.

Data fetching

Fetching data is straightforward. Let’s fetch some data here in App.js.

// src/App.js
import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
import './app.css';
import './app.scss';
import styled from 'styled-components';
import Users from './components/Users';

const StyledApp = styled.div`
  text-transform: uppercase;
`;

function App() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    fetch('https://randomuser.me/api/?format=json&results=10')
      .then(res => res.json())
      .then(res => {
        const results = res.results.map(({ picture, name }) => {
          return {
            thumbnail: picture.thumbnail,
            name: `${name.title} ${name.first} ${name.last}`,
          };
        });

        setUsers([...results]);
      });
  }, []);

  return (
    <StyledApp>
      <h1 className='clr-red bg-clr-black'>Hello World!</h1>
      <Users {...{ users }} />
    </StyledApp>
  );
}

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

You also need to update Users.js:

// src/components/Users.js
import React from 'react';
import styled from 'styled-components';
import PropTypes from 'prop-types';

const StyledUsers = styled.div`
  .card {
    display: flex;
    border: 1px solid salmon;
    padding: 20px;
  }

  .photo {
    margin-right: 20px;
    display: flex;
    align-items: flex-start;
  }

  .img {
    border-radius: 50%;
  }
`;

function Users({ users }) {
  return (
    <StyledUsers>
      {users.map((user, index) => (
        <div className='card' key={index}>
          <div className='photo'>
            <img className='img' src={user.thumbnail} alt={user.name} />
          </div>
          <h1 className='name'>{user.name}</h1>
        </div>
      ))}
    </StyledUsers>
  );
}

Users.propTypes = {
  users: PropTypes.array,
};

export default Users;

If you wanted, we could also use Axios or Next’s SWR.

ESLint may alert you about prop-types. First, install it with the command yarn add prop-types -D and update Users.js accordingly.

The users list should now be displayed:

List of users.

You can check the include-data-fetching branch for the latest code.

The final code is in the final branch.

Conclusion

That’s a lot of build tools if you don’t know where to look, and even if you do, bringing them together to run your app can be tedious. Most especially when you have to keep up with various updates and backwards dependencies. And at this point, my guess is that you’re probably thinking: why bother? Why can’t you just use one of the already-made tool-chain, so you can focus on what really matters – building your app?

And to be fair, there’s a reasonable justification there.

But there’s power in knowing the inner workings of how these tools works, because then you can determine what resource is important to your development and vice-versa. Also, you could create a boilerplate just like React Boilerplate so as to avoid doing this over. You might also want to check out Create App wizard to learn more or a different way on how these toolchains can be wired up.

There are many more tools you can add to your toolchain, like routing (with React Router) and form management (with Formik), but none of these are needed to make React work so we didn’t touch on them.

Your toolchain is yours to use and mutate as you deem fit. And there’s nothing stopping you from having one. So, why not create one?

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Adebiyi Adedotun Caught in the web, breaking things and learning fast.

Leave a Reply