Make Commit in Your React Project Format-Test-Build Ready with Husky - A Step-by-Step Guide
Tags: react, webdev, tutorial, productivity
Published: 2024-05-24 12:57:50

> How often have you encountered build failures, test failures lint errors, or inconsistent code formatting after committing your code?

In this article, I will show you how to avoid those issues step by step using `husky` and `lint-staged`.

Before diving in, let's explorer what each tool is used for.

- [Husky](https://typicode.github.io/husky/): It enables us to easily use [git hooks](https://git-scm.com/docs/githooks). We can define commands when a specific behavior happens. We will use `pre-commit`, which is hooked before creating a commit.
- [Eslint](https://eslint.org/): It analyzes our code to quickly find problems. We will use the default setup provided by Vite.
- [Prettier](https://prettier.io/): It makes our code prettier by formatting. It supports many languages and editors.
- [lint-staged](https://github.com/lint-staged/lint-staged): It enables us to run linters against staged git files. `eslint` and `prettier` will be executed by it.

I will use `pnpm` as a package manager, but feel free to use any package you prefer.

---

## 1\. Set Up React Project With Vite

```
> pnpm create vite
```

![created vite project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cd49t297du96y7llcz6j.png)

After setting a project, let's install the dependencies by executing the command `pnpm install`.

Run the dev server, and let's see if the project was successfully installed.

![Start Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wucggste03kiwdvwtsj0.png)

---

## 2\. Set Up Vitest for React

Let's write some test code to be executed during `pre-commit` stage. I will use [vitest](https://vitest.dev/) and [testing-library](https://testing-library.com/) in this project to test our code.

```
> pnpm install -D @testing-library/jest-dom vitest jsdom @testing-library/react
```

Create the `vitest.config.ts` file to configure the test environment.

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

export default defineConfig(({ mode }) => ({
  plugins: [react()],
  resolve: {
    conditions: mode === 'test' ? ['browser'] : [],
  },
  test: {
    environment: 'jsdom',
    setupFiles: ['./vitest-setup.js'],
  },
}));
```

Create the `vitest-setup.js` file, which enables us to utilize features for testing React components.

```javascript
import '@testing-library/jest-dom/vitest';
```

Add a type in `tsconfig.json` file to provide the necessary types for DOM testing.

```
...
    "types": ["@testing-library/jest-dom"],
...
```

---

## 3\. Write Test Code

Now, we are ready to test React components. Let's test our `App` component.

Create the `App.test.tsx` file in the same directory where `App.tsx` file is.

```typescript
import { describe, test, expect } from 'vitest';
import { render } from '@testing-library/react'

import App from './App';

describe('App',()=>{
  test('count should be increased when the button is clicked.', async () => {
    const app = render(<App />);

    const button = await app.findByText(/^count/);
    expect(button.textContent).toBe('count is 0');

    await button.click();

    expect(button.textContent).toBe('count is 1');
  });
});
```

Add `test` script into `package.json` file. We run `vitest` file with the option [run](https://vitest.dev/guide/#command-line-interface) to execute once.

```json
...
    "test": "vitest run"
...
```

Let's test our code by the command `pnpm run test`.

![test](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/480jtnfemj2h2u8z32y3.png)

---

## 4\. Set Up Prettier

Install `prettier`.

```
> pnpm install -D prettier
```

Create the `.prettierrc` file at the root, which serves as the [prettier configuration file](https://prettier.io/docs/en/configuration.html).

```json
{
  "trailingComma": "es5",
  "tabWidth": 2,
  "semi": true,
  "singleQuote": true
}
```

Let's format `App.tsx` file using `prettier` to see if it works.

```
> npx prettier --write ./src/App.tsx
```

Before -

![before formatting](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wkuo80x26c069jd3y3gf.png)

After - 

![after formatting](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lszr510nd3pwptm6g1o2.png)

---

## 5\. Set Up lint-staged

Install `lint-staged`.

```
>  pnpm install --save-dev lint-staged
```

Create the `.lintstagedrc` file which is a configuration file for lint-staged.

```json
{
  "src/**/*.{ts,tsx}": [
    "eslint --fix",
    "prettier --write"
  ]
}
```

I will lint only typescript files but you can configure it whatever you want.

To test git-staged files, we need to initialize `git` first.

```
> git init
```

Let's test with one file, `main.tsx`. 

```
> git add ./src/main.tsx
> npx lint-staged
```

Before -

![before lint-staged](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7gusfr1myvx24mpmwgkq.png)

After -

![After lint-staged](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s8m89ju61b0tw1je2int.png)

![lint-staged console output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lfh4zjpcxcatjij44inm.png)

As you can see, the code is formatted by `prettier`.
I will make an error in the `main.ts` file and add it to the git staged.
Let's see if `eslint` lets us know there is a problem.

![unused import package](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qz0k2dzonv40xcqhmje7.png)

![eslint error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0yovddp0wv9q3u8t3v6q.png)

`eslint` successfully found the problem. 

---

## 6\. Set Up husky

Install `husky`.

```
> pnpm install --save-dev husky
```

Initialize husky

```
> npx husky init
```

After this command, you will see the `.husky` directory under the root. You don't need to worry about the `_` folder, it comes with `husky` when installed and handles the git hooks for us.

![husky folder](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ww7lerq8xddpgo9mtuig.png)

Let's customize the `pre-commit` hook. 

[pre-commit]
```
pnpm run test
npx lint-staged
pnpm run build
```

The commit will be ignored if one of those commands fails.

Let's add all the files and make a commit.

![pre-commit fails](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0qw1kmznsfh005pt0ur7.png)

Our commit failed because of the unused imported package we added in the last step, and as you can see, the commit is ignored since `eslint` throws an error.

Let's get rid of the error and create a commit again.

![first output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d76gau3as8n4aivlpi9p.png)

![second output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5mrxl9um2fgr86is9o3r.png)

Our new commit is successfully done.

---

## Wrap up

Now, each commit guarantees you that the commit is formatted, tested, and buildable.
Keep in mind that the configuration can be different depending on your project or your goal. Since we could write any command in `pre-commit`, it could do more complex tasks.

I hope you found this article helpful.

Happy Coding!