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.
We’ll be building a toolchain that includes:
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.
react-app-toolchain
repoTo 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:
- public - index.html - src - components - Users.js - App.js - app.css - app.scss - .eslintrc - .prettierignore - .prettierrc - .gitignore - package.json
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.
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.
You can check the
include-bundler
branch for the latest code.
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 theeditor.formatOnSave
settings. When you save, Prettier will automatically format your file — even without theformat
script.You can check the
include-formatting
branch for the latest code.
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 codeeslint-config-prettier
settles the conflict between ESLint and Prettiereslint-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 imageseslint-plugin-react
helps with react specific ESLint rulesThen 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.
There are many options to go with when it comes to styling. Let’s go over:
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 inpackage.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.
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 updateUsers.js
accordingly.
The users list should now be displayed:
You can check the
include-data-fetching
branch for the latest code.
The final code is in the final
branch.
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?
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowExplore use cases for using npm vs. npx such as long-term dependency management or temporary tasks and running packages on the fly.
Validating and auditing AI-generated code reduces code errors and ensures that code is compliant.
Build a real-time image background remover in Vue using Transformers.js and WebGPU for client-side processing with privacy and efficiency.
Optimize search parameter handling in React and Next.js with nuqs for SEO-friendly, shareable URLs and a better user experience.