React and typescript components lib, part 4: pre-commit with Husky and lint-staged

Introduction Portuguese version: Biblioteca de componentes React e typescript, parte 4: pré-commit com Husky e lint-staged In part four, pre-commit actions will be added to the application using two libraries: Husky and lint-staged. The goal is to demonstrate the practical implementation of these tools in this article. As a reference, here is the article Husky and lint-staged for pre-commit in React, which was the first one I wrote on dev.to. Libs Husky: is the library that allows pre-commit actions to be executed lint-staged: is the library that defines which pre-commit actions will be executed and applied specifically to the files staged in git — that is, the files that have been added using git add Pre-commit action Pre-commit actions are executed after running git commit in the terminal, but right before the commit is actually finalized. For the application, three pre-commit actions will be defined: Format the files staged in git according to the linting rules, right before the commit is finalized Format the files staged in git according to the prettier rules, right before the commit is finalized Run the tests related to the modified components that have been staged, to validate that they are passing, right before the commit is finalized Setup Husky Adding Husky: yarn add husky --dev At the time of writing this article, the following version was generated: "husky": "^9.1.7" To configure it, run the following command in the terminal: npx husky init This will generate a .husky folder at the root of the project. Inside it, there will be a _ folder containing all available git hooks, although they are not yet configured to run in the project. Outside of the _ folder, there will be a pre-commit file with an initial action already defined. However, since it doesn’t match the application's needs, this file will be modified. In addition to generating the folder mentioned above, a command will also be added to package.json to install Husky inside the app: "scripts": { //... "prepare": "husky" }, Setup lint-staged Adding lint-staged: yarn add lint-staged --dev At the time of writing this article, the following version was generated: "lint-staged": "^15.5.0" To configure Husky to run pre-commit actions together with lint-staged, it is necessary to modify the file inside the .husky folder: pre-commit npx --no-install lint-staged Pre-commit actions Currently, the app has three commands related to the actions to be added to the pre-commit: package.json //... "scripts": { //... "lint-src-fix": "eslint src --fix", "format-src-fix": "prettier src --write", "test": "jest" } By setting up these actions to run, the pre-commit will work successfully: the files staged in git will be updated right before the commit is finalized. However, the downside is that it will run the command on all files in src, even though the pre-commit is supposed to impact only the files that are staged in git. To make the pre-commit actions more efficient, two new scripts will be created: package.json //... "scripts": { //... "lint-fix": "eslint --fix", "format-fix": "prettier --write" } If you try to run these commands directly in the terminal, they won't work successfully because they require a file or folder path to be passed, like: yarn lint-fix {path to folder or file} yarn format-fix {path to folder or file} However, when these commands are run with lint-staged, it detects the files that are staged in git and successfully applies the commands to them. To define the pre-commit actions to be executed through Husky with lint-staged, you need to add the following to package.json: "lint-staged": { "yarn lint-fix", "yarn format-fix", "yarn test --findRelatedTests --bail" }, For the tests, two additional options are passed: --findRelatedTests: runs the tests for the files that are staged in git as well as the ones related to them. By "related," I mean that if you modify and git add a component file like Component.tsx without touching its corresponding test file Component.test.tsx, this command will still know which test is associated with the component and validate it. --bail: stops running tests immediately after the first failure, aborting the commit process Now, the pre-commit actions are more efficient, acting only on the files staged in git. But we can make it even more specific by targeting file extensions for each action. For code formatting and linting, the goal is to apply the rules only to typeScript files .ts and .tsx inside the components folder. For running tests, we will target only .tsx files. Thus, for lint-staged to not only consider the files staged in git but also apply actions based on the file types we want, the configuration will look like this: "lint-staged": { "src/components/**/*.{ts,tsx}": [ "yarn lint-fix", "yarn

Apr 30, 2025 - 01:03
 0
React and typescript components lib, part 4: pre-commit with Husky and lint-staged

Introduction

Portuguese version: Biblioteca de componentes React e typescript, parte 4: pré-commit com Husky e lint-staged

In part four, pre-commit actions will be added to the application using two libraries: Husky and lint-staged. The goal is to demonstrate the practical implementation of these tools in this article. As a reference, here is the article Husky and lint-staged for pre-commit in React, which was the first one I wrote on dev.to.

Libs

  • Husky: is the library that allows pre-commit actions to be executed
  • lint-staged: is the library that defines which pre-commit actions will be executed and applied specifically to the files staged in git — that is, the files that have been added using git add

Pre-commit action

Pre-commit actions are executed after running git commit in the terminal, but right before the commit is actually finalized.
For the application, three pre-commit actions will be defined:

  • Format the files staged in git according to the linting rules, right before the commit is finalized
  • Format the files staged in git according to the prettier rules, right before the commit is finalized
  • Run the tests related to the modified components that have been staged, to validate that they are passing, right before the commit is finalized

Setup Husky

Adding Husky:

yarn add husky --dev

At the time of writing this article, the following version was generated:

"husky": "^9.1.7"

To configure it, run the following command in the terminal:

npx husky init

This will generate a .husky folder at the root of the project. Inside it, there will be a _ folder containing all available git hooks, although they are not yet configured to run in the project. Outside of the _ folder, there will be a pre-commit file with an initial action already defined. However, since it doesn’t match the application's needs, this file will be modified.
In addition to generating the folder mentioned above, a command will also be added to package.json to install Husky inside the app:

"scripts": {
  //...
  "prepare": "husky"
},

Setup lint-staged

Adding lint-staged:

yarn add lint-staged --dev

At the time of writing this article, the following version was generated:

"lint-staged": "^15.5.0"

To configure Husky to run pre-commit actions together with lint-staged, it is necessary to modify the file inside the .husky folder:

  • pre-commit
npx --no-install lint-staged

Pre-commit actions

Currently, the app has three commands related to the actions to be added to the pre-commit:

  • package.json
//...
"scripts": {
  //...
  "lint-src-fix": "eslint src --fix",
  "format-src-fix": "prettier src --write",
  "test": "jest"
}

By setting up these actions to run, the pre-commit will work successfully: the files staged in git will be updated right before the commit is finalized. However, the downside is that it will run the command on all files in src, even though the pre-commit is supposed to impact only the files that are staged in git.
To make the pre-commit actions more efficient, two new scripts will be created:

  • package.json
//...
"scripts": {
  //...
  "lint-fix": "eslint --fix",
  "format-fix": "prettier --write"
}

If you try to run these commands directly in the terminal, they won't work successfully because they require a file or folder path to be passed, like:

yarn lint-fix {path to folder or file}
yarn format-fix {path to folder or file}

However, when these commands are run with lint-staged, it detects the files that are staged in git and successfully applies the commands to them.

To define the pre-commit actions to be executed through Husky with lint-staged, you need to add the following to package.json:

  "lint-staged": {
    "yarn lint-fix",
    "yarn format-fix",
    "yarn test --findRelatedTests --bail"
  },

For the tests, two additional options are passed:

  • --findRelatedTests: runs the tests for the files that are staged in git as well as the ones related to them. By "related," I mean that if you modify and git add a component file like Component.tsx without touching its corresponding test file Component.test.tsx, this command will still know which test is associated with the component and validate it.
  • --bail: stops running tests immediately after the first failure, aborting the commit process

Now, the pre-commit actions are more efficient, acting only on the files staged in git. But we can make it even more specific by targeting file extensions for each action.
For code formatting and linting, the goal is to apply the rules only to typeScript files .ts and .tsx inside the components folder. For running tests, we will target only .tsx files.
Thus, for lint-staged to not only consider the files staged in git but also apply actions based on the file types we want, the configuration will look like this:

  "lint-staged": {
    "src/components/**/*.{ts,tsx}": [
      "yarn lint-fix",
      "yarn format-fix"
    ],
    "src/components/**/*.tsx": "yarn test --findRelatedTests --bail"
  },

package.json

Currently, the package.json looks like this:

{
  "name": "react-example-lib",
  "version": "0.3.0",
  "main": "dist/cjs/index.js",
  "module": "dist/esm/index.js",
  "types": "dist/index.d.ts",
  "files": [
    "dist"
  ],
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/griseduardo/react-example-lib.git"
  },
  "scripts": {
    "build": "rollup -c --bundleConfigAsCjs",
    "lint-src": "eslint src",
    "lint-src-fix": "eslint src --fix",
    "lint-fix": "eslint --fix",
    "format-src": "prettier src --check",
    "format-src-fix": "prettier src --write",
    "format-fix": "prettier --write",
    "test": "jest",
    "prepare": "husky"
  },
  "lint-staged": {
    "src/components/**/*.{ts,tsx}": [
      "yarn lint-fix",
      "yarn format-fix"
    ],
    "src/components/**/*.tsx": "yarn test --findRelatedTests --bail"
  },
  "devDependencies": {
    "@babel/core": "^7.26.10",
    "@babel/preset-env": "^7.26.9",
    "@babel/preset-react": "^7.26.3",
    "@babel/preset-typescript": "^7.26.0",
    "@eslint/js": "^9.19.0",
    "@rollup/plugin-commonjs": "^28.0.2",
    "@rollup/plugin-node-resolve": "^16.0.0",
    "@rollup/plugin-terser": "^0.4.4",
    "@rollup/plugin-typescript": "11.1.6",
    "@testing-library/dom": "^10.4.0",
    "@testing-library/jest-dom": "^6.6.3",
    "@testing-library/react": "^16.2.0",
    "@types/jest": "^29.5.14",
    "@types/react": "^19.0.10",
    "@types/react-dom": "^19.0.4",
    "eslint": "^9.19.0",
    "eslint-plugin-react": "^7.37.4",
    "husky": "^9.1.7",
    "jest": "^29.7.0",
    "jest-environment-jsdom": "^29.7.0",
    "jest-styled-components": "^7.2.0",
    "lint-staged": "^15.5.0",
    "prettier": "^3.4.2",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "rollup": "^4.30.1",
    "rollup-plugin-dts": "^6.1.1",
    "rollup-plugin-peer-deps-external": "^2.2.4",
    "styled-components": "^6.1.14",
    "typescript": "^5.7.3",
    "typescript-eslint": "^8.23.0"
  },
  "peerDependencies": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "styled-components": "^6.1.14"
  }
}

The version will be updated to 0.4.0, since a new version of the library will be released:

{
  "name": "react-example-lib",
  "version": "0.4.0",
  "main": "dist/cjs/index.js",
  "module": "dist/esm/index.js",
  "types": "dist/index.d.ts",
  "files": [
    "dist"
  ],
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/griseduardo/react-example-lib.git"
  },
  "scripts": {
    "build": "rollup -c --bundleConfigAsCjs",
    "lint-src": "eslint src",
    "lint-src-fix": "eslint src --fix",
    "lint-fix": "eslint --fix",
    "format-src": "prettier src --check",
    "format-src-fix": "prettier src --write",
    "format-fix": "prettier --write",
    "test": "jest",
    "prepare": "husky"
  },
  "lint-staged": {
    "src/components/**/*.{ts,tsx}": [
      "yarn lint-fix",
      "yarn format-fix"
    ],
    "src/components/**/*.tsx": "yarn test --findRelatedTests --bail"
  },
  "devDependencies": {
    "@babel/core": "^7.26.10",
    "@babel/preset-env": "^7.26.9",
    "@babel/preset-react": "^7.26.3",
    "@babel/preset-typescript": "^7.26.0",
    "@eslint/js": "^9.19.0",
    "@rollup/plugin-commonjs": "^28.0.2",
    "@rollup/plugin-node-resolve": "^16.0.0",
    "@rollup/plugin-terser": "^0.4.4",
    "@rollup/plugin-typescript": "11.1.6",
    "@testing-library/dom": "^10.4.0",
    "@testing-library/jest-dom": "^6.6.3",
    "@testing-library/react": "^16.2.0",
    "@types/jest": "^29.5.14",
    "@types/react": "^19.0.10",
    "@types/react-dom": "^19.0.4",
    "eslint": "^9.19.0",
    "eslint-plugin-react": "^7.37.4",
    "husky": "^9.1.7",
    "jest": "^29.7.0",
    "jest-environment-jsdom": "^29.7.0",
    "jest-styled-components": "^7.2.0",
    "lint-staged": "^15.5.0",
    "prettier": "^3.4.2",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "rollup": "^4.30.1",
    "rollup-plugin-dts": "^6.1.1",
    "rollup-plugin-peer-deps-external": "^2.2.4",
    "styled-components": "^6.1.14",
    "typescript": "^5.7.3",
    "typescript-eslint": "^8.23.0"
  },
  "peerDependencies": {
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "styled-components": "^6.1.14"
  }
}

CHANGELOG file

Currently, the CHANGELOG.md looks like this:

## 0.3.0

_Mar. 24, 2025_

- setup jest and testing-library
- add components tests

## 0.2.0

_Fev. 24, 2025_

- setup typescript-eslint and prettier
- add custom rules

## 0.1.0

_Jan. 29, 2025_

- initial config

Since a new version will be released, the changes will be added to the file:

## 0.4.0

_Abr. 29, 2025_

- setup husky and lint-staged
- define pre-commit actions

## 0.3.0

_Mar. 24, 2025_

- setup jest and testing-library
- add components tests

## 0.2.0

_Fev. 24, 2025_

- setup typescript-eslint and prettier
- add custom rules

## 0.1.0

_Jan. 29, 2025_

- initial config

Folder structure

The folder structure is currently as follows. In addition to the modification of some files, the .husky folder with the pre-commit file was also added:

Image description

Publication of a new version

The first step is to check if the rollup build runs successfully. To do this, run yarn build in the terminal, as defined in package.json. If the build is successful, proceed to publish the new version of the library with: npm publish --access public

Conclusion

The goal of this article was to setup Husky and lint-staged to define pre-commit rules for the application. The objective is to ensure that code changes adhere to lint and prettier standardization rules, and validate that the tests continue to pass before finalizing the commit. Here is the repository on github and the library on npmjs with the new modifications.