Skip to content

Commit 5b77361

Browse files
committed
initial commit to make plugin work
0 parents  commit 5b77361

File tree

12 files changed

+2927
-0
lines changed

12 files changed

+2927
-0
lines changed

.github/workflows/test.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: Test
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Setup Node.js
17+
uses: actions/setup-node@v4
18+
with:
19+
node-version: '18'
20+
21+
- name: Install pnpm
22+
uses: pnpm/action-setup@v3
23+
with:
24+
version: 8
25+
26+
- name: Get pnpm store directory
27+
shell: bash
28+
run: |
29+
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
30+
31+
- name: Setup pnpm cache
32+
uses: actions/cache@v4
33+
with:
34+
path: ${{ env.STORE_PATH }}
35+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
36+
restore-keys: |
37+
${{ runner.os }}-pnpm-store-
38+
39+
- name: Install dependencies
40+
run: pnpm install
41+
42+
- name: Run tests
43+
run: pnpm test
44+
45+
- name: Run build
46+
run: pnpm build

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
dist

README.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# eslint-plugin-react-query-must-invalidate-queries
2+
3+
ESLint plugin to enforce best practices when working with [TanStack Query](https://tanstack.com/query/latest)
4+
5+
## Installation
6+
7+
```bash
8+
npm install eslint-plugin-react-query-must-invalidate-queries --save-dev
9+
# or
10+
yarn add eslint-plugin-react-query-must-invalidate-queries --dev
11+
# or
12+
pnpm add eslint-plugin-react-query-must-invalidate-queries --save-dev
13+
```
14+
15+
## Usage
16+
17+
Add `react-query-must-invalidate-queries` to the plugins section of your `.eslintrc` configuration file:
18+
19+
```js
20+
{
21+
"plugins": ["react-query-must-invalidate-queries"],
22+
"rules": {
23+
"react-query-must-invalidate-queries/require-mutation-invalidation": "error" // or "warn"
24+
}
25+
}
26+
```
27+
28+
## Rules
29+
30+
### require-mutation-invalidation
31+
32+
🔧 This rule enforces that `useMutation` hooks include an `onSuccess` callback that
33+
calls `invalidateQueries` to ensure data consistency.
34+
35+
#### Why?
36+
37+
When using mutations in React Query, it's important to invalidate related queries
38+
after a successful mutation to ensure the UI reflects the latest server state without
39+
having to do a lot of mental prep.
40+
41+
This rule helps prevent stale data by ensuring that mutations properly invalidate affected queries.
42+
43+
#### Rule Details
44+
45+
This rule enforces:
46+
47+
- Presence of an `onSuccess` callback in `useMutation` hooks
48+
- The `onSuccess` callback must include a call to `invalidateQueries`
49+
50+
✅ Examples of **correct** code:
51+
52+
```js
53+
useMutation({
54+
mutationFn: updateUser,
55+
onSuccess: () => {
56+
queryClient.invalidateQueries({ queryKey: ["users"] });
57+
},
58+
});
59+
60+
useMutation({
61+
mutationFn: updateUser,
62+
onSuccess: () => {
63+
const { invalidateQueries } = queryClient;
64+
invalidateQueries({ queryKey: ["users"] });
65+
},
66+
});
67+
68+
useMutation({
69+
mutationFn: updateUser,
70+
onSuccess: async () => {
71+
await someOtherOperation();
72+
await queryClient.invalidateQueries({ queryKey: ["users"] });
73+
},
74+
});
75+
```
76+
77+
❌ Examples of **incorrect** code:
78+
79+
```js
80+
useMutation({
81+
mutationFn: updateUser,
82+
});
83+
84+
useMutation({
85+
mutationFn: updateUser,
86+
onSuccess: () => {
87+
console.log("Success!");
88+
},
89+
});
90+
91+
useMutation({
92+
mutationFn: updateUser,
93+
onSuccess: () => {},
94+
});
95+
```
96+
97+
#### When Not To Use It
98+
99+
You might want to disable this rule if:
100+
101+
- You have mutations that intentionally don't need to invalidate any queries (e.g., analytics-only mutations)
102+
- You're handling cache updates through other means like `setQueryData`
103+
- You have a specific caching strategy that doesn't require query invalidation
104+
105+
## Contributing
106+
107+
Contributions are welcome! Please read our [contributing guidelines](CONTRIBUTING.md) to get started.
108+
109+
## License
110+
111+
MIT
112+
113+
## Resources
114+
115+
- [Rule Documentation](docs/rules/require-mutation-invalidation.md)
116+
- [TanStack Query Mutations](https://tanstack.com/query/latest/docs/react/guides/mutations)
117+
- [TanStack Query Invalidation from Mutations](https://tanstack.com/query/latest/docs/react/guides/invalidations-from-mutations)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Enforce invalidateQueries in useMutation onSuccess callbacks (require-mutation-invalidation)
2+
3+
This rule enforces that `useMutation` hooks include an `onSuccess` callback that calls `invalidateQueries` to ensure data consistency.
4+
5+
## Rule Details
6+
7+
When using mutations in React Query, it's important to invalidate related queries after a successful mutation to ensure the UI reflects the latest server state. This rule enforces that `useMutation` hooks include an `onSuccess` callback that calls `invalidateQueries`.
8+
9+
### ❌ Examples of incorrect code
10+
11+
```js
12+
useMutation({
13+
mutationFn: updateUser,
14+
});
15+
16+
useMutation({
17+
mutationFn: updateUser,
18+
onSuccess: () => {
19+
console.log("Success!");
20+
},
21+
});
22+
23+
useMutation({
24+
mutationFn: updateUser,
25+
onSuccess: () => {},
26+
});
27+
```
28+
29+
### ✅ Examples of correct code
30+
31+
```js
32+
useMutation({
33+
mutationFn: updateUser,
34+
onSuccess: () => {
35+
queryClient.invalidateQueries({ queryKey: ["users"] });
36+
},
37+
});
38+
39+
useMutation({
40+
mutationFn: updateUser,
41+
onSuccess: () => {
42+
const { invalidateQueries } = queryClient;
43+
invalidateQueries({ queryKey: ["users"] });
44+
},
45+
});
46+
47+
useMutation({
48+
mutationFn: updateUser,
49+
onSuccess: async () => {
50+
await someOtherOperation();
51+
await queryClient.invalidateQueries({ queryKey: ["users"] });
52+
},
53+
});
54+
55+
useMutation({
56+
mutationFn: updateUser,
57+
onSuccess: () => {
58+
const invalidate = queryClient.invalidateQueries;
59+
invalidate({ queryKey: ["users"] });
60+
},
61+
});
62+
```
63+
64+
## When Not To Use It
65+
66+
If you have mutations that intentionally don't need to invalidate any queries (e.g., analytics-only mutations) or if you're handling cache updates through other means like `setQueryData`, you might want to disable this rule.
67+
68+
## Further Reading
69+
70+
- [React Query Mutations - TanStack Query docs](https://tanstack.com/query/latest/docs/react/guides/mutations)
71+
- [Invalidation from Mutations - TanStack Query docs](https://tanstack.com/query/latest/docs/react/guides/invalidations-from-mutations)
72+

package.json

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "eslint-plugin-react-query-must-invalidate-queries",
3+
"version": "0.0.1",
4+
"description": "ESLint plugin for React query keys to enforce queries invalidation after a mutation",
5+
"main": "dist/index.js",
6+
"files": [
7+
"dist"
8+
],
9+
"scripts": {
10+
"build": "tsc",
11+
"test": "vitest",
12+
"test:coverage": "vitest run --coverage",
13+
"prepublishOnly": "pnpm run build"
14+
},
15+
"keywords": [
16+
"eslint",
17+
"eslintplugin",
18+
"eslint-plugin",
19+
"react-query",
20+
"query-keys"
21+
],
22+
"peerDependencies": {
23+
"eslint": ">=9.0.0"
24+
},
25+
"engines": {
26+
"node": ">=18"
27+
},
28+
"devDependencies": {
29+
"@types/eslint": "^9.6.1",
30+
"@types/node": "^22.13.5",
31+
"@typescript-eslint/parser": "^8.24.1",
32+
"@typescript-eslint/rule-tester": "^8.24.1",
33+
"@typescript-eslint/utils": "^8.24.1",
34+
"@vitest/coverage-v8": "^3.0.6",
35+
"eslint": "^9.21.0",
36+
"typescript": "^5.7.3",
37+
"vitest": "^3.0.6"
38+
}
39+
}

0 commit comments

Comments
 (0)