tducasse's blog

Create-react-app with VS Code

June 16, 2019

Requirements

Note: I uploaded the final code on GitHub.

Create a new project using create-react-app

create-react-app provides a very easy way to generate a React app in seconds. This is currently developed by Facebook, and the recommended way to start a new project. Open a terminal and go to the desired folder (create-react-app will create a folder for your app).

cd ~/Projects # this is my projects folder
npx create-react-app my-app # replace `my-app` with the name of your app
cd myapp
npm start

You should see something like this: React app starting screen

Folder structure

The script will generate a few files, and it should look something like this: Folder architecture

Let’s have a look at what’s been created:

  1. package.json: This file is related to npm. It holds our dependencies (the libraries you want to be able to use in your code). You can also describe your application, add useful scripts in order to automate common tasks, such as running tests, starting a server, deploying, etc.
  2. package-lock.json: This is auto-generated by npm every time you add a new dependency. You should never have to touch it, and it is good practice to commit it to your repository.
  3. .gitignore: When you commit files to a git repository, git will (by default) want to add every file that’s in your project. But sometimes you might want to exclude a file, and .gitignore is exactlly that. By default, create-react-app excludes node_modules for example, which is the folder containing all our dependencies. Since we are going to commit package.json and package-lock.json anyway, there is no need at all to commit them (plus they are super heavy).
  4. public folder: This is where we find the root of our application, index.html, and our favicon, favicon.ico.
  5. src folder: Our app folder. This is where our code lives, and where we are going to spend 98% of our time.

Let’s have a look at the code

First, let’s go to index.html, and have a look at this line:

...
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
...

This will be the HTML element to which the React application will be attached.

index.js is the first file that will be loaded in our application.

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

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

This line is the one that actually links our React app to the DOM (root node, as we’ve seen just before).

import React from 'react';import ReactDOM from 'react-dom';import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

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

These two first lines are everything that’s necessary to write React code.

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';import App from './App';import * as serviceWorker from './serviceWorker';

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

These ones are just importing our React components (here App), and our CSS files.

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));

This last line is used only if you are writing a PWA (Progressive Web App, more on this here). Yes, create-react-app is already configured as a PWA!

So what is this App.js doing? Let’s have a look:

import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

At the beginning, you can see the standard import React from 'react' that you need to include in every file that uses jsx (this cool HTML-like syntax that we use to write React components). You may have noticed that we import a file called App.css. Open it, and look at what’s written here:

.App {
  text-align: center;
}

.App-logo {
  animation: App-logo-spin infinite 20s linear;
  height: 40vmin;
  pointer-events: none;
}

.App-header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

.App-link {
  color: #61dafb;
}

@keyframes App-logo-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

I’m not going to spend too much time on this, but let’s take the example of the first class:

.App {
  text-align: center;
}

And let’s see how we use it in a React component:

function App() {
  return (
    <div className="App">      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

As you can see, the main difference with “normal” HTML is that the name of the attribute is className, instead of class.

Configuring VS Code

Enough about create-react-app and the code it generated, it’s time to configure our code editor. VS Code has pretty good defaults (syntax highlighting, autocompletion, ‘go to definition’, etc). But you can make it even better!

ESlint

Since JavaScript is not a compiled language, there is nothing that can tell us if something looks like it’s going to fail before we actually run it. ESLint solves exactly this problem.

ESLint is the most popular JavaScript linter. A linter is a program that analyses your code, and tries to find potential errors, ahead of runtime. ESLint is highly configurable, and you can extend premade sets of rules, define your own rules, override existing rules, etc.

Luckily, create-react-app already comes with ESlint. So we don’t have much to do!

Install the ESLint extension for VS Code. This will allow us to see the result of ESLint directly in VS Code.

Once installed, you can quickly test it. Go to App.js again, and remove this line:

<a
    className="App-link"
    href="https://reactjs.org"
    target="_blank"
    rel="noopener noreferrer"  >

VS Code should start yelling at you: ESLint saying you need noopener and noreferrer

Prettier

Prettier is an opinionated code formatter. People used to fight for hours around commas, semi-colons, etc. Prettier is an attempt at ending the debate. Since most editors have a Prettier plugin, you can “autoformat on save”, which means you can write ugly code, and never worry about formatting!

You can use Prettier in different ways. My favorite one is to run it as part of the ESLint rules.

First, install the Prettier dependencies:

npm i -D prettier eslint-config-prettier eslint-plugin-prettier

Then, create a file, at the root of your app, called .eslintrc.json as such:

{
  "extends": [
    "react-app",
    "prettier",
    "prettier/react"
  ],
  "plugins": [
    "prettier"
  ],
  "rules": {
    "prettier/prettier": "error"
  }
}

So what have we done?

  • eslint-config-prettier is a package that allows us to disable the rules that would conflict with the rules defined by Prettier.
  • eslint-plugin-prettier is the one that allows to run Prettier as an ESLint rule.

If you have a look at the .eslintrc.json file that we just created, you’ll notice that we have added a rule that basically says “everything that Prettier reports should be treated as an error”.

Once everything is saved, go back to App.js and have a look at what ESLint is saying now: ESLint reports Prettier errors as errors

Autoformatting

So this is all good, now we see what’s wrong about our code, but wouldn’t it be nice if we could just fix everything automatically? Replacing double quotes with single quotes should be pretty straightforward for a computer, right?

ESLint has an option to autofix every error that can be autofixed. On the command-line, it’s --fix, and you can configure your VS Code so that this happens everytime you save.

Create a new folder, at the root of your app, called .vscode, and inside this folder, a file called settings.json:

{
  "eslint.autoFixOnSave": true
}

Go back to App.js, and try saving your file now, it should get reformatted instantly!

Precommit hook

So now that we have linting and formatting all sorted, what happens if someone decides to contribute to our code without setting up everything we just set up? It will break everything, and you will be back to coding-style hell. So what can we do about it?

Two packages will help us with that:

  • husky gives us a really easy way to set up git hooks.
  • lint-staged will lint the files that are ready to be committed.

First, install them with:

npm i -D lint-staged husky

Go to your package.json and add:

"lint-staged": {
    "**/*.js": [
      "eslint --fix",
      "git add"
    ]
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  }

And you’re done! Now, every time you will try to commit unformatted files, husky will launch lint-staged, which will intercept the process and run ESLint first. And if there’s an error ESLint can’t fix, it will stop the whole process. Which means you can’t commit code that doesn’t work anymore!

Absolute imports

It is very common in a React project to organise your code in multiple nested folders.

Let’s say we decide to implement something like Brad Frost’s atomic design for example. A common way to implement it would be (this example is on GitHub): Atomic Design example folder structure

App.js would import the LoginPage template like this:

import React from "react";
import LoginPage from "./components/templates/LoginPage";
function App() {
  return (
    <div style={{ padding: 20 }}>
      <LoginPage />
    </div>
  );
}

export default App;

This is all good! But now, if you go to LoginPage.js, and have a look at how we have to import LoginPassword:

import React, { useState } from "react";
import LoginPassword from "../organisms/LoginPassword";
const LoginPage = props => {
  return (
    <>
      <LoginPassword />
    </>
  );
};

export default LoginPage;

Notice the ../ to go up a folder? This will become really hard to follow once we start deeply nesting folders and files.

A good solution for that is something called absolute imports. At the root of your app, create a file called jsconfig.json:

{
  "compilerOptions": {
    "baseUrl": "./src/"
  },
  "include": [
    "src"
  ]
}

Now you can import your files using an absolute path, starting from ./src/:

import React from "react";
import LoginPassword from "components/organisms/LoginPassword";
const LoginPage = () => {
  return (
    <>
      <LoginPassword />
    </>
  );
};

export default LoginPage;

While it might not seem like a big deal right now, because we only have one level of nesting, big applications tend to have very deeply nested components, which makes imports look like ../../../../... very quickly!

Find the final code on GitHub.


Thibaud Ducasse

Hey, I'm Thibaud, a Melbourne based software engineer.
Find me on Twitter and GitHub.