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!