How to setup a react project from scratch in 2024

This article will guide you on how to setup a solid, professional grade react project in 2024. We will be using vite and a set of libraries and configs

By the end of the article, you will have a project that is fast and easy to run, uses modern libraries and makes development and testing easier.

It is aimed at readers who have some experience with react. It will teach you to create and customize your own react setup or upgrade an existing project. If you want to learn how to set up a modern, professional development environment this article is for you!

Base Project

We start by using vite to create a base project. using the command line run:

npm create vite@latest my_project -- --template react

now we install the dependencies using:

cd my_project
npm i

By default vite uses npm run dev to run the project. But I'm a fan of using npm start, if you want to enable this go to the package.json file and in the "scripts" section add:

 "start": "vite",

This lets npm know to run vite when you type npm start

Add Testing

To add testing to our project, we will need vitest to run our tests, and the react testing library to enable us to render React components on our tests. We will also add some configurations to connect them and improve our experience. Vitest is a test runner, a direct substitute for the more common jest. But runs faster making our development process smoother.

Installing vitest

On the command line run:

npm install -D vitest

On the package.json file "scripts" section add:

"test": "vitest --watch",
"test-run": "vitest --run",

Adding a test coverage report:

Now let's add a test coverage reporting tool.

npm install -D @vitest/coverage-v8

package.json file "scripts" section:

"coverage": "vitest run --coverage"

to verify that it is working, run:

npm run coverage

This will tell you how much of your react code is covered by tests.

Usually, we don't want to have our coverage report on our git repository, so on our .gitignore file add the line coverage to ignore the coverage folder.

Adding jsdom environment

Since we are creating one React project to run on the browser, our tests need to be able to access the browser APIs. Things like window, DOM, web storage, etc. To help us do this we use jsdom.

npm i -D jsdom

create a vitest.config.js file and add this configuration to it:

import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
	globals: true, // so you don't need to import describe/test/expect
	environment: 'jsdom',
  },
});

This lets vitest know that we want to use the jsdom environment in out tests, and gives us access to virtual browser API0.s. AKA, now vitest runs in a simulated browser environment. You can now do things like accessing the document object and creating dom elements in your tests:

describe('jsdom', () => {
  it('can create a basic dom element', () => {
  const  element  =  document.createElement('div');
  expect(element).not.toBeNull();
  document.body.appendChild(element);
  });
});

jsdom test matchers

When we write our tests, it's great to have test matchers that helps us test frontend code. Matchers such as expect(el).toHaveClass(a) or expect(input).toHaveFocus() Luckily jest-dom lets us do just that.

So let's add it to our project: npm install --save-dev @testing-library/jest-dom

Now we need to create a file called setupTests.js and add the following code to it:

import '@testing-library/jest-dom';

On the file vitest.config.js let's point our configuration to it:

import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true, // so you don't need to import describe/test/expect
    environment: 'jsdom',
    setupFiles: './setupTests.js',
  },
});

Now all these test matchers are available to you!

As an example, we can now easily check if an element exist on the page:

describe('jsdom', () => {
  it('can create a basic dom element', () => {
    const element = document.createElement('div');
    expect(element).not.toBeNull();
    document.body.appendChild(element);
    expect(element).toBeInTheDocument();
  });
});

Add react testing library

Now we can test dom nodes! But we want to use React components! For that, we need React-testing-library. React-testing-library builds on top of the DOM Testing Library and adds APIs for working with React components in our tests.

Let's add it by running:

npm install -D @testing-library/react

Now add to the vitest.config.js file the react plugin:

import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';

export default defineConfig({
  test: {
    globals: true, // so you don't need to import describe/test/expect
    environment: 'jsdom',
    setupFiles: './setupTests.js',
  },
  plugins: [react()],
});

Now you can test your react components! As an example for the component

export const  MyComponent  = () =>  <p>Hello world</p>;

You can test this as such:

import { screen, render } from '@testing-library/react';
import { MyComponent } from './my-component';

describe('react testing', () => {
  it('render a component', () => {
    render(<MyComponent />);
    const text = screen.getByText('Hello world');
    expect(text).toBeInTheDocument();
  });
});

[Optional] Mock Service Worker

Mock Service Worker is a library to mock server requests API calls. This is a great way to mock the API responses from a server, a common need for frontend projects. With Mock Service Worker we can mock those API calls and run our tests without making real API calls. We can also control exactly what their return value is.

To install it, run: npm install -D msw@latest

Creating a mock server and handlers

We are going to need two new files: ./server-mocks/handlers.js and ./server-mocks/server.js

./server-mocks/handlers.js is where you add you mock API handlers. for example, if in our code we call fetch("http://example.com/movies.json") we can mock that call for our tests as:

import { http, HttpResponse } from 'msw';

export const handlers = [
  http.get('https://pokeapi.co/api/v2/pokemon', () => {
    return HttpResponse.json({
      count: 1,
      next: 'https://pokeapi.co/api/v2/pokemon?offset=20&limit=20',
      previous: null,
      results: [
        {
          name: 'bulbasaur',
          url: 'https://pokeapi.co/api/v2/pokemon/1/',
        },
      ],
    });
  }),
];

./server-mocks/server.js is the file where we setup the mock server.

import { setupServer } from 'msw/node';
import { handlers } from './handlers';

export const server = setupServer(...handlers);

On the setupTests.js file add:

import '@testing-library/jest-dom';
import { server } from './server-mocks/server';
import { afterAll, afterEach, beforeAll } from 'vitest';

beforeAll(() => {
  server.listen();
});

afterEach(() => {
  server.resetHandlers();
});

afterAll(() => {
  server.close();
});

Now any call your components make to 'https://pokeapi.co/api/v2/pokemon' returns our mocked data instead when we run our tests.

ESLint and prettier

Finally let's add eslint and prettier to help us catch errors and have consistent code formatting across all files.

ESLint is a static code analysis tool that identifies potential problems in our JavaScript Code. (a simple example is to identify an undeclared variable being used). It's very useful to catch potential errors while writing our code.

Installing ESLint

run: npm i -D eslint npm i -D eslint-plugin-vitest

ESLint will show errors on our test files about certain global variables it doesn't know exist. He thinks this is an error since they are not declared or imported, but are provided by vitest To avoid ESLint complaining about the vitest globals in our test, let's add them to the ESLint configuration file:

.eslintrc.cjs :

const vitest = require('eslint-plugin-vitest');
module.exports = {
  root: true,
  env: { browser: true, es2020: true },
  extends: [
    'eslint:recommended',
    'plugin:react/recommended',
    'plugin:react/jsx-runtime',
    'plugin:react-hooks/recommended',
  ],
  ignorePatterns: ['dist', '.eslintrc.cjs'],
  parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
  settings: { react: { version: '18.2' } },
  plugins: ['react-refresh'],
  rules: {
    'react/jsx-no-target-blank': 'off',
    'react-refresh/only-export-components': [
      'warn',
      { allowConstantExport: true },
    ],
  },
  globals: {
    ...vitest.environments.env.globals,
  },
};

Adding ESLint to npm scripts:

On package.json scripts add:

  "lint": "eslint ./src  --ext js,jsx --report-unused-disable-directives --max-warnings 0",

Installing prettier

Prettier lets us automaticlay "prettify" our code, making thinks like identation width, commas and quotes standard across all of our code.

To install prettier run: npm install --save-dev --save-exact prettier

Now create the file .prettierrc and add:

{
  "trailingComma": "all",
  "tabWidth": 2,
  "semi": true,
  "singleQuote": true,
  "printWidth": 90,
  "useTabs": false,
  "bracketSpacing": true
}

You can customise it to your preference, see more examples here.

On package.json scripts add:

"prettier": "prettier ./src/** --check",
"prettier-fix": "prettier ./src/** --write"

If you use VScode as an editor, you can configure it to automatically code format on save according to the configuration you just created. No more tabs vs spaces discussions!

Finally, automate it on commit!

Sometimes we forget to fix some eslint issue or format our code with prettier before creating a commit. But worry not! We can automate it! With git hooks - we can run eslint and prettier before committing. With husky and lint-staged we can setup a git hook that automatically runs our eslint and prettier each time we commit code!

Let's run: npm install --save-dev husky lint-staged

and then: npx husky init

now change the file .husky/pre-commit to look like:

echo "starting pre-commit script"
npx lint-staged
echo "ending pre-commit script"

And add to package.json :

  "scripts": {...},
  "lint-staged": {
    "**/*": [
      "eslint ./src --ext js,jsx",
      "prettier --write --ignore-unknown",
    ]
  },

If you want, you can setup some other npm scripts to run each time you create a commit :)

Here is a quick list of the npm commands that are now available in your project:

 // runs the react development server:
 npm run start
 npm run dev: 'vite',
 
 // builds the js bundle:
 npm run build
 
 // runs eslint on all js/jsx files:
 npm run lint
 
// runs prettier:
 npm run prettier
 
 // runs prettier and automatically formats files
 npm run prettier-fix
 
 // runs vitest ( on watch mode )
 npm run test

// creates a code coverage report:
 npm run coverage

If you run into any problem you can check the final result here: https://github.com/paulosilva-dev/react-vite-starter

Well done! Now you have a solid react project setup, that automates a lot of tasks, makes it easy to create and run tests, and is a breeze to work with!

Hope that you learned something! :)

The project will work well as is, but feel free to go to the linked docs and learn a bit more about each library and customize it for your own needs!

ReactTestingViteFrontendProject
Avatar for Paulo Silva

Written by Paulo Silva

Loading

Fetching comments

Hey! 👋

Got something to say?

or to leave a comment.