Skip to content

Commit e8c6362

Browse files
authored
[eslint-plugin-react-hooks] Add ESLint v10 support (#35720)
## Summary ESLint v10.0.0 was released on February 7, 2026. The current `peerDependencies` for `eslint-plugin-react-hooks` only allows up to `^9.0.0`, which causes peer dependency warnings when installing with ESLint v10. This PR: - Adds `^10.0.0` to the eslint peer dependency range - Adds `eslint-v10` to devDependencies for testing - Adds an `eslint-v10` e2e fixture (based on the existing `eslint-v9` fixture) ESLint v10's main breaking changes (removal of legacy eslintrc config, deprecated context methods) don't affect this plugin - flat config is already supported since v7.0.0, and the deprecated APIs already have fallbacks in place. ## How did you test this change? Ran the existing unit test suite: ``` cd packages/eslint-plugin-react-hooks && yarn test ``` All 5082 tests passed.
1 parent 03ca38e commit e8c6362

8 files changed

Lines changed: 265 additions & 1 deletion

File tree

‎.github/workflows/runtime_eslint_plugin_e2e.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ jobs:
2929
- "7"
3030
- "8"
3131
- "9"
32+
- "10"
3233
steps:
3334
- uses: actions/checkout@v4
3435
with:

‎fixtures/eslint-v10/README.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# ESLint v10 Fixture
2+
3+
This fixture allows us to test e2e functionality for `eslint-plugin-react-hooks` with eslint version 10.
4+
5+
Run the following to test.
6+
7+
```sh
8+
cd fixtures/eslint-v10
9+
yarn
10+
yarn build
11+
yarn lint
12+
```

‎fixtures/eslint-v10/build.mjs‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env node
2+
3+
import{execSync}from'node:child_process';
4+
import{dirname,resolve}from'node:path';
5+
import{fileURLToPath}from'node:url';
6+
7+
const__filename=fileURLToPath(import.meta.url);
8+
const__dirname=dirname(__filename);
9+
10+
execSync('yarn build -r stable eslint-plugin-react-hooks',{
11+
cwd: resolve(__dirname,'..','..'),
12+
stdio: 'inherit',
13+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import{defineConfig}from'eslint/config';
2+
importreactHooksfrom'eslint-plugin-react-hooks';
3+
4+
exportdefaultdefineConfig([
5+
reactHooks.configs.flat['recommended-latest'],
6+
{
7+
languageOptions: {
8+
ecmaVersion: 'latest',
9+
sourceType: 'module',
10+
parserOptions: {
11+
ecmaFeatures: {
12+
jsx: true,
13+
},
14+
},
15+
},
16+
rules: {
17+
'react-hooks/exhaustive-deps': 'error',
18+
},
19+
},
20+
]);

‎fixtures/eslint-v10/index.js‎

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/**
2+
* Exhaustive Deps
3+
*/
4+
// Valid because dependencies are declared correctly
5+
functionComment({comment, commentSource}){
6+
constcurrentUserID=comment.viewer.id;
7+
constenvironment=RelayEnvironment.forUser(currentUserID);
8+
constcommentID=nullthrows(comment.id);
9+
useEffect(()=>{
10+
constsubscription=SubscriptionCounter.subscribeOnce(
11+
`StoreSubscription_${commentID}`,
12+
()=>
13+
StoreSubscription.subscribe(
14+
environment,
15+
{
16+
comment_id: commentID,
17+
},
18+
currentUserID,
19+
commentSource
20+
)
21+
);
22+
return()=>subscription.dispose();
23+
},[commentID,commentSource,currentUserID,environment]);
24+
}
25+
26+
// Valid because no dependencies
27+
functionUseEffectWithNoDependencies(){
28+
constlocal={};
29+
useEffect(()=>{
30+
console.log(local);
31+
});
32+
}
33+
functionUseEffectWithEmptyDependencies(){
34+
useEffect(()=>{
35+
constlocal={};
36+
console.log(local);
37+
},[]);
38+
}
39+
40+
// OK because `props` wasn't defined.
41+
functionComponentWithNoPropsDefined(){
42+
useEffect(()=>{
43+
console.log(props.foo);
44+
},[]);
45+
}
46+
47+
// Valid because props are declared as a dependency
48+
functionComponentWithPropsDeclaredAsDep({foo}){
49+
useEffect(()=>{
50+
console.log(foo.length);
51+
console.log(foo.slice(0));
52+
},[foo]);
53+
}
54+
55+
// Valid because individual props are declared as dependencies
56+
functionComponentWithIndividualPropsDeclaredAsDeps(props){
57+
useEffect(()=>{
58+
console.log(props.foo);
59+
console.log(props.bar);
60+
},[props.bar,props.foo]);
61+
}
62+
63+
// Invalid because neither props or props.foo are declared as dependencies
64+
functionComponentWithoutDeclaringPropAsDep(props){
65+
useEffect(()=>{
66+
console.log(props.foo);
67+
// eslint-disable-next-line react-hooks/exhaustive-deps
68+
},[]);
69+
useCallback(()=>{
70+
console.log(props.foo);
71+
// eslint-disable-next-line react-hooks/exhaustive-deps
72+
},[]);
73+
// eslint-disable-next-line react-hooks/void-use-memo
74+
useMemo(()=>{
75+
console.log(props.foo);
76+
// eslint-disable-next-line react-hooks/exhaustive-deps
77+
},[]);
78+
React.useEffect(()=>{
79+
console.log(props.foo);
80+
// eslint-disable-next-line react-hooks/exhaustive-deps
81+
},[]);
82+
React.useCallback(()=>{
83+
console.log(props.foo);
84+
// eslint-disable-next-line react-hooks/exhaustive-deps
85+
},[]);
86+
// eslint-disable-next-line react-hooks/void-use-memo
87+
React.useMemo(()=>{
88+
console.log(props.foo);
89+
// eslint-disable-next-line react-hooks/exhaustive-deps
90+
},[]);
91+
React.notReactiveHook(()=>{
92+
console.log(props.foo);
93+
},[]);// This one isn't a violation
94+
}
95+
96+
/**
97+
* Rules of Hooks
98+
*/
99+
// Valid because functions can call functions.
100+
functionnormalFunctionWithConditionalFunction(){
101+
if(cond){
102+
doSomething();
103+
}
104+
}
105+
106+
// Valid because hooks can call hooks.
107+
functionuseHook(){
108+
useState();
109+
}
110+
constwhatever=functionuseHook(){
111+
useState();
112+
};
113+
constuseHook1=()=>{
114+
useState();
115+
};
116+
letuseHook2=()=>useState();
117+
useHook2=()=>{
118+
useState();
119+
};
120+
121+
// Invalid because hooks can't be called in conditionals.
122+
functionComponentWithConditionalHook(){
123+
if(cond){
124+
// eslint-disable-next-line react-hooks/rules-of-hooks
125+
useConditionalHook();
126+
}
127+
}
128+
129+
// Invalid because hooks can't be called in loops.
130+
functionuseHookInLoops(){
131+
while(a){
132+
// eslint-disable-next-line react-hooks/rules-of-hooks
133+
useHook1();
134+
if(b)return;
135+
// eslint-disable-next-line react-hooks/rules-of-hooks
136+
useHook2();
137+
}
138+
while(c){
139+
// eslint-disable-next-line react-hooks/rules-of-hooks
140+
useHook3();
141+
if(d)return;
142+
// eslint-disable-next-line react-hooks/rules-of-hooks
143+
useHook4();
144+
}
145+
}
146+
147+
/**
148+
* Compiler Rules
149+
*/
150+
// Invalid: component factory
151+
functionInvalidComponentFactory(){
152+
constDynamicComponent=()=><div>Hello</div>;
153+
// eslint-disable-next-line react-hooks/static-components
154+
return<DynamicComponent/>;
155+
}
156+
157+
// Invalid: mutating globals
158+
functionInvalidGlobals(){
159+
// eslint-disable-next-line react-hooks/immutability
160+
window.myGlobal=42;
161+
return<div>Done</div>;
162+
}
163+
164+
// Invalid: useMemo with wrong deps
165+
functionInvalidUseMemo({items}){
166+
// eslint-disable-next-line react-hooks/exhaustive-deps
167+
constsorted=useMemo(()=>[...items].sort(),[]);
168+
return<div>{sorted.length}</div>;
169+
}
170+
171+
// Invalid: missing/extra deps in useEffect
172+
functionInvalidEffectDeps({a, b}){
173+
useEffect(()=>{
174+
console.log(a);
175+
// eslint-disable-next-line react-hooks/exhaustive-deps
176+
},[]);
177+
178+
useEffect(()=>{
179+
console.log(a);
180+
// TODO: eslint-disable-next-line react-hooks/exhaustive-effect-dependencies
181+
},[a,b]);
182+
}

‎fixtures/eslint-v10/package.json‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"private": true,
3+
"name": "eslint-v10",
4+
"dependencies": {
5+
"eslint": "^10.0.0",
6+
"eslint-plugin-react-hooks": "link:../../build/oss-stable/eslint-plugin-react-hooks",
7+
"jiti": "^2.4.2"
8+
},
9+
"scripts": {
10+
"build": "node build.mjs && yarn",
11+
"lint": "tsc --noEmit && eslint index.js --report-unused-disable-directives"
12+
},
13+
"devDependencies": {
14+
"typescript": "^5.4.3"
15+
}
16+
}

‎fixtures/eslint-v10/tsconfig.json‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"compilerOptions": {
3+
"lib": [
4+
"es2022"
5+
],
6+
"module": "nodenext",
7+
"moduleResolution": "nodenext",
8+
"target": "es2022",
9+
"typeRoots": [
10+
"./node_modules/@types"
11+
],
12+
"skipLibCheck": true
13+
},
14+
"exclude": [
15+
"node_modules",
16+
"**/node_modules",
17+
"../node_modules",
18+
"../../node_modules"
19+
]
20+
}

‎packages/eslint-plugin-react-hooks/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
},
3737
"homepage": "https://react.dev/",
3838
"peerDependencies": {
39-
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
39+
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0"
4040
},
4141
"dependencies": {
4242
"@babel/core": "^7.24.4",

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
[eslint-plugin-react-hooks] Add ESLint v10 support (#35720) · react/react@e8c6362 · GitHub
Skip to content

Commit e8c6362

Browse files
authored
[eslint-plugin-react-hooks] Add ESLint v10 support (#35720)
## Summary ESLint v10.0.0 was released on February 7, 2026. The current `peerDependencies` for `eslint-plugin-react-hooks` only allows up to `^9.0.0`, which causes peer dependency warnings when installing with ESLint v10. This PR: - Adds `^10.0.0` to the eslint peer dependency range - Adds `eslint-v10` to devDependencies for testing - Adds an `eslint-v10` e2e fixture (based on the existing `eslint-v9` fixture) ESLint v10's main breaking changes (removal of legacy eslintrc config, deprecated context methods) don't affect this plugin - flat config is already supported since v7.0.0, and the deprecated APIs already have fallbacks in place. ## How did you test this change? Ran the existing unit test suite: ``` cd packages/eslint-plugin-react-hooks && yarn test ``` All 5082 tests passed.
1 parent 03ca38e commit e8c6362

8 files changed

Lines changed: 265 additions & 1 deletion

File tree

‎.github/workflows/runtime_eslint_plugin_e2e.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ jobs:
2929
- "7"
3030
- "8"
3131
- "9"
32+
- "10"
3233
steps:
3334
- uses: actions/checkout@v4
3435
with:

‎fixtures/eslint-v10/README.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# ESLint v10 Fixture
2+
3+
This fixture allows us to test e2e functionality for `eslint-plugin-react-hooks` with eslint version 10.
4+
5+
Run the following to test.
6+
7+
```sh
8+
cd fixtures/eslint-v10
9+
yarn
10+
yarn build
11+
yarn lint
12+
```

‎fixtures/eslint-v10/build.mjs‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env node
2+
3+
import{execSync}from'node:child_process';
4+
import{dirname,resolve}from'node:path';
5+
import{fileURLToPath}from'node:url';
6+
7+
const__filename=fileURLToPath(import.meta.url);
8+
const__dirname=dirname(__filename);
9+
10+
execSync('yarn build -r stable eslint-plugin-react-hooks',{
11+
cwd: resolve(__dirname,'..','..'),
12+
stdio: 'inherit',
13+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import{defineConfig}from'eslint/config';
2+
importreactHooksfrom'eslint-plugin-react-hooks';
3+
4+
exportdefaultdefineConfig([
5+
reactHooks.configs.flat['recommended-latest'],
6+
{
7+
languageOptions: {
8+
ecmaVersion: 'latest',
9+
sourceType: 'module',
10+
parserOptions: {
11+
ecmaFeatures: {
12+
jsx: true,
13+
},
14+
},
15+
},
16+
rules: {
17+
'react-hooks/exhaustive-deps': 'error',
18+
},
19+
},
20+
]);

‎fixtures/eslint-v10/index.js‎

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/**
2+
* Exhaustive Deps
3+
*/
4+
// Valid because dependencies are declared correctly
5+
functionComment({comment, commentSource}){
6+
constcurrentUserID=comment.viewer.id;
7+
constenvironment=RelayEnvironment.forUser(currentUserID);
8+
constcommentID=nullthrows(comment.id);
9+
useEffect(()=>{
10+
constsubscription=SubscriptionCounter.subscribeOnce(
11+
`StoreSubscription_${commentID}`,
12+
()=>
13+
StoreSubscription.subscribe(
14+
environment,
15+
{
16+
comment_id: commentID,
17+
},
18+
currentUserID,
19+
commentSource
20+
)
21+
);
22+
return()=>subscription.dispose();
23+
},[commentID,commentSource,currentUserID,environment]);
24+
}
25+
26+
// Valid because no dependencies
27+
functionUseEffectWithNoDependencies(){
28+
constlocal={};
29+
useEffect(()=>{
30+
console.log(local);
31+
});
32+
}
33+
functionUseEffectWithEmptyDependencies(){
34+
useEffect(()=>{
35+
constlocal={};
36+
console.log(local);
37+
},[]);
38+
}
39+
40+
// OK because `props` wasn't defined.
41+
functionComponentWithNoPropsDefined(){
42+
useEffect(()=>{
43+
console.log(props.foo);
44+
},[]);
45+
}
46+
47+
// Valid because props are declared as a dependency
48+
functionComponentWithPropsDeclaredAsDep({foo}){
49+
useEffect(()=>{
50+
console.log(foo.length);
51+
console.log(foo.slice(0));
52+
},[foo]);
53+
}
54+
55+
// Valid because individual props are declared as dependencies
56+
functionComponentWithIndividualPropsDeclaredAsDeps(props){
57+
useEffect(()=>{
58+
console.log(props.foo);
59+
console.log(props.bar);
60+
},[props.bar,props.foo]);
61+
}
62+
63+
// Invalid because neither props or props.foo are declared as dependencies
64+
functionComponentWithoutDeclaringPropAsDep(props){
65+
useEffect(()=>{
66+
console.log(props.foo);
67+
// eslint-disable-next-line react-hooks/exhaustive-deps
68+
},[]);
69+
useCallback(()=>{
70+
console.log(props.foo);
71+
// eslint-disable-next-line react-hooks/exhaustive-deps
72+
},[]);
73+
// eslint-disable-next-line react-hooks/void-use-memo
74+
useMemo(()=>{
75+
console.log(props.foo);
76+
// eslint-disable-next-line react-hooks/exhaustive-deps
77+
},[]);
78+
React.useEffect(()=>{
79+
console.log(props.foo);
80+
// eslint-disable-next-line react-hooks/exhaustive-deps
81+
},[]);
82+
React.useCallback(()=>{
83+
console.log(props.foo);
84+
// eslint-disable-next-line react-hooks/exhaustive-deps
85+
},[]);
86+
// eslint-disable-next-line react-hooks/void-use-memo
87+
React.useMemo(()=>{
88+
console.log(props.foo);
89+
// eslint-disable-next-line react-hooks/exhaustive-deps
90+
},[]);
91+
React.notReactiveHook(()=>{
92+
console.log(props.foo);
93+
},[]);// This one isn't a violation
94+
}
95+
96+
/**
97+
* Rules of Hooks
98+
*/
99+
// Valid because functions can call functions.
100+
functionnormalFunctionWithConditionalFunction(){
101+
if(cond){
102+
doSomething();
103+
}
104+
}
105+
106+
// Valid because hooks can call hooks.
107+
functionuseHook(){
108+
useState();
109+
}
110+
constwhatever=functionuseHook(){
111+
useState();
112+
};
113+
constuseHook1=()=>{
114+
useState();
115+
};
116+
letuseHook2=()=>useState();
117+
useHook2=()=>{
118+
useState();
119+
};
120+
121+
// Invalid because hooks can't be called in conditionals.
122+
functionComponentWithConditionalHook(){
123+
if(cond){
124+
// eslint-disable-next-line react-hooks/rules-of-hooks
125+
useConditionalHook();
126+
}
127+
}
128+
129+
// Invalid because hooks can't be called in loops.
130+
functionuseHookInLoops(){
131+
while(a){
132+
// eslint-disable-next-line react-hooks/rules-of-hooks
133+
useHook1();
134+
if(b)return;
135+
// eslint-disable-next-line react-hooks/rules-of-hooks
136+
useHook2();
137+
}
138+
while(c){
139+
// eslint-disable-next-line react-hooks/rules-of-hooks
140+
useHook3();
141+
if(d)return;
142+
// eslint-disable-next-line react-hooks/rules-of-hooks
143+
useHook4();
144+
}
145+
}
146+
147+
/**
148+
* Compiler Rules
149+
*/
150+
// Invalid: component factory
151+
functionInvalidComponentFactory(){
152+
constDynamicComponent=()=><div>Hello</div>;
153+
// eslint-disable-next-line react-hooks/static-components
154+
return<DynamicComponent/>;
155+
}
156+
157+
// Invalid: mutating globals
158+
functionInvalidGlobals(){
159+
// eslint-disable-next-line react-hooks/immutability
160+
window.myGlobal=42;
161+
return<div>Done</div>;
162+
}
163+
164+
// Invalid: useMemo with wrong deps
165+
functionInvalidUseMemo({items}){
166+
// eslint-disable-next-line react-hooks/exhaustive-deps
167+
constsorted=useMemo(()=>[...items].sort(),[]);
168+
return<div>{sorted.length}</div>;
169+
}
170+
171+
// Invalid: missing/extra deps in useEffect
172+
functionInvalidEffectDeps({a, b}){
173+
useEffect(()=>{
174+
console.log(a);
175+
// eslint-disable-next-line react-hooks/exhaustive-deps
176+
},[]);
177+
178+
useEffect(()=>{
179+
console.log(a);
180+
// TODO: eslint-disable-next-line react-hooks/exhaustive-effect-dependencies
181+
},[a,b]);
182+
}

‎fixtures/eslint-v10/package.json‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"private": true,
3+
"name": "eslint-v10",
4+
"dependencies": {
5+
"eslint": "^10.0.0",
6+
"eslint-plugin-react-hooks": "link:../../build/oss-stable/eslint-plugin-react-hooks",
7+
"jiti": "^2.4.2"
8+
},
9+
"scripts": {
10+
"build": "node build.mjs && yarn",
11+
"lint": "tsc --noEmit && eslint index.js --report-unused-disable-directives"
12+
},
13+
"devDependencies": {
14+
"typescript": "^5.4.3"
15+
}
16+
}

‎fixtures/eslint-v10/tsconfig.json‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"compilerOptions": {
3+
"lib": [
4+
"es2022"
5+
],
6+
"module": "nodenext",
7+
"moduleResolution": "nodenext",
8+
"target": "es2022",
9+
"typeRoots": [
10+
"./node_modules/@types"
11+
],
12+
"skipLibCheck": true
13+
},
14+
"exclude": [
15+
"node_modules",
16+
"**/node_modules",
17+
"../node_modules",
18+
"../../node_modules"
19+
]
20+
}

‎packages/eslint-plugin-react-hooks/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
},
3737
"homepage": "https://react.dev/",
3838
"peerDependencies": {
39-
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
39+
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0"
4040
},
4141
"dependencies": {
4242
"@babel/core": "^7.24.4",

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [eslint-plugin-react-hooks] Add ESLint v10 support (#35720) · react/react@e8c6362 · GitHub
Skip to content

Commit e8c6362

Browse files
authored
[eslint-plugin-react-hooks] Add ESLint v10 support (#35720)
## Summary ESLint v10.0.0 was released on February 7, 2026. The current `peerDependencies` for `eslint-plugin-react-hooks` only allows up to `^9.0.0`, which causes peer dependency warnings when installing with ESLint v10. This PR: - Adds `^10.0.0` to the eslint peer dependency range - Adds `eslint-v10` to devDependencies for testing - Adds an `eslint-v10` e2e fixture (based on the existing `eslint-v9` fixture) ESLint v10's main breaking changes (removal of legacy eslintrc config, deprecated context methods) don't affect this plugin - flat config is already supported since v7.0.0, and the deprecated APIs already have fallbacks in place. ## How did you test this change? Ran the existing unit test suite: ``` cd packages/eslint-plugin-react-hooks && yarn test ``` All 5082 tests passed.
1 parent 03ca38e commit e8c6362

8 files changed

Lines changed: 265 additions & 1 deletion

File tree

‎.github/workflows/runtime_eslint_plugin_e2e.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ jobs:
2929
- "7"
3030
- "8"
3131
- "9"
32+
- "10"
3233
steps:
3334
- uses: actions/checkout@v4
3435
with:

‎fixtures/eslint-v10/README.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# ESLint v10 Fixture
2+
3+
This fixture allows us to test e2e functionality for `eslint-plugin-react-hooks` with eslint version 10.
4+
5+
Run the following to test.
6+
7+
```sh
8+
cd fixtures/eslint-v10
9+
yarn
10+
yarn build
11+
yarn lint
12+
```

‎fixtures/eslint-v10/build.mjs‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env node
2+
3+
import{execSync}from'node:child_process';
4+
import{dirname,resolve}from'node:path';
5+
import{fileURLToPath}from'node:url';
6+
7+
const__filename=fileURLToPath(import.meta.url);
8+
const__dirname=dirname(__filename);
9+
10+
execSync('yarn build -r stable eslint-plugin-react-hooks',{
11+
cwd: resolve(__dirname,'..','..'),
12+
stdio: 'inherit',
13+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import{defineConfig}from'eslint/config';
2+
importreactHooksfrom'eslint-plugin-react-hooks';
3+
4+
exportdefaultdefineConfig([
5+
reactHooks.configs.flat['recommended-latest'],
6+
{
7+
languageOptions: {
8+
ecmaVersion: 'latest',
9+
sourceType: 'module',
10+
parserOptions: {
11+
ecmaFeatures: {
12+
jsx: true,
13+
},
14+
},
15+
},
16+
rules: {
17+
'react-hooks/exhaustive-deps': 'error',
18+
},
19+
},
20+
]);

‎fixtures/eslint-v10/index.js‎

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/**
2+
* Exhaustive Deps
3+
*/
4+
// Valid because dependencies are declared correctly
5+
functionComment({comment, commentSource}){
6+
constcurrentUserID=comment.viewer.id;
7+
constenvironment=RelayEnvironment.forUser(currentUserID);
8+
constcommentID=nullthrows(comment.id);
9+
useEffect(()=>{
10+
constsubscription=SubscriptionCounter.subscribeOnce(
11+
`StoreSubscription_${commentID}`,
12+
()=>
13+
StoreSubscription.subscribe(
14+
environment,
15+
{
16+
comment_id: commentID,
17+
},
18+
currentUserID,
19+
commentSource
20+
)
21+
);
22+
return()=>subscription.dispose();
23+
},[commentID,commentSource,currentUserID,environment]);
24+
}
25+
26+
// Valid because no dependencies
27+
functionUseEffectWithNoDependencies(){
28+
constlocal={};
29+
useEffect(()=>{
30+
console.log(local);
31+
});
32+
}
33+
functionUseEffectWithEmptyDependencies(){
34+
useEffect(()=>{
35+
constlocal={};
36+
console.log(local);
37+
},[]);
38+
}
39+
40+
// OK because `props` wasn't defined.
41+
functionComponentWithNoPropsDefined(){
42+
useEffect(()=>{
43+
console.log(props.foo);
44+
},[]);
45+
}
46+
47+
// Valid because props are declared as a dependency
48+
functionComponentWithPropsDeclaredAsDep({foo}){
49+
useEffect(()=>{
50+
console.log(foo.length);
51+
console.log(foo.slice(0));
52+
},[foo]);
53+
}
54+
55+
// Valid because individual props are declared as dependencies
56+
functionComponentWithIndividualPropsDeclaredAsDeps(props){
57+
useEffect(()=>{
58+
console.log(props.foo);
59+
console.log(props.bar);
60+
},[props.bar,props.foo]);
61+
}
62+
63+
// Invalid because neither props or props.foo are declared as dependencies
64+
functionComponentWithoutDeclaringPropAsDep(props){
65+
useEffect(()=>{
66+
console.log(props.foo);
67+
// eslint-disable-next-line react-hooks/exhaustive-deps
68+
},[]);
69+
useCallback(()=>{
70+
console.log(props.foo);
71+
// eslint-disable-next-line react-hooks/exhaustive-deps
72+
},[]);
73+
// eslint-disable-next-line react-hooks/void-use-memo
74+
useMemo(()=>{
75+
console.log(props.foo);
76+
// eslint-disable-next-line react-hooks/exhaustive-deps
77+
},[]);
78+
React.useEffect(()=>{
79+
console.log(props.foo);
80+
// eslint-disable-next-line react-hooks/exhaustive-deps
81+
},[]);
82+
React.useCallback(()=>{
83+
console.log(props.foo);
84+
// eslint-disable-next-line react-hooks/exhaustive-deps
85+
},[]);
86+
// eslint-disable-next-line react-hooks/void-use-memo
87+
React.useMemo(()=>{
88+
console.log(props.foo);
89+
// eslint-disable-next-line react-hooks/exhaustive-deps
90+
},[]);
91+
React.notReactiveHook(()=>{
92+
console.log(props.foo);
93+
},[]);// This one isn't a violation
94+
}
95+
96+
/**
97+
* Rules of Hooks
98+
*/
99+
// Valid because functions can call functions.
100+
functionnormalFunctionWithConditionalFunction(){
101+
if(cond){
102+
doSomething();
103+
}
104+
}
105+
106+
// Valid because hooks can call hooks.
107+
functionuseHook(){
108+
useState();
109+
}
110+
constwhatever=functionuseHook(){
111+
useState();
112+
};
113+
constuseHook1=()=>{
114+
useState();
115+
};
116+
letuseHook2=()=>useState();
117+
useHook2=()=>{
118+
useState();
119+
};
120+
121+
// Invalid because hooks can't be called in conditionals.
122+
functionComponentWithConditionalHook(){
123+
if(cond){
124+
// eslint-disable-next-line react-hooks/rules-of-hooks
125+
useConditionalHook();
126+
}
127+
}
128+
129+
// Invalid because hooks can't be called in loops.
130+
functionuseHookInLoops(){
131+
while(a){
132+
// eslint-disable-next-line react-hooks/rules-of-hooks
133+
useHook1();
134+
if(b)return;
135+
// eslint-disable-next-line react-hooks/rules-of-hooks
136+
useHook2();
137+
}
138+
while(c){
139+
// eslint-disable-next-line react-hooks/rules-of-hooks
140+
useHook3();
141+
if(d)return;
142+
// eslint-disable-next-line react-hooks/rules-of-hooks
143+
useHook4();
144+
}
145+
}
146+
147+
/**
148+
* Compiler Rules
149+
*/
150+
// Invalid: component factory
151+
functionInvalidComponentFactory(){
152+
constDynamicComponent=()=><div>Hello</div>;
153+
// eslint-disable-next-line react-hooks/static-components
154+
return<DynamicComponent/>;
155+
}
156+
157+
// Invalid: mutating globals
158+
functionInvalidGlobals(){
159+
// eslint-disable-next-line react-hooks/immutability
160+
window.myGlobal=42;
161+
return<div>Done</div>;
162+
}
163+
164+
// Invalid: useMemo with wrong deps
165+
functionInvalidUseMemo({items}){
166+
// eslint-disable-next-line react-hooks/exhaustive-deps
167+
constsorted=useMemo(()=>[...items].sort(),[]);
168+
return<div>{sorted.length}</div>;
169+
}
170+
171+
// Invalid: missing/extra deps in useEffect
172+
functionInvalidEffectDeps({a, b}){
173+
useEffect(()=>{
174+
console.log(a);
175+
// eslint-disable-next-line react-hooks/exhaustive-deps
176+
},[]);
177+
178+
useEffect(()=>{
179+
console.log(a);
180+
// TODO: eslint-disable-next-line react-hooks/exhaustive-effect-dependencies
181+
},[a,b]);
182+
}

‎fixtures/eslint-v10/package.json‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"private": true,
3+
"name": "eslint-v10",
4+
"dependencies": {
5+
"eslint": "^10.0.0",
6+
"eslint-plugin-react-hooks": "link:../../build/oss-stable/eslint-plugin-react-hooks",
7+
"jiti": "^2.4.2"
8+
},
9+
"scripts": {
10+
"build": "node build.mjs && yarn",
11+
"lint": "tsc --noEmit && eslint index.js --report-unused-disable-directives"
12+
},
13+
"devDependencies": {
14+
"typescript": "^5.4.3"
15+
}
16+
}

‎fixtures/eslint-v10/tsconfig.json‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"compilerOptions": {
3+
"lib": [
4+
"es2022"
5+
],
6+
"module": "nodenext",
7+
"moduleResolution": "nodenext",
8+
"target": "es2022",
9+
"typeRoots": [
10+
"./node_modules/@types"
11+
],
12+
"skipLibCheck": true
13+
},
14+
"exclude": [
15+
"node_modules",
16+
"**/node_modules",
17+
"../node_modules",
18+
"../../node_modules"
19+
]
20+
}

‎packages/eslint-plugin-react-hooks/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
},
3737
"homepage": "https://react.dev/",
3838
"peerDependencies": {
39-
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
39+
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0"
4040
},
4141
"dependencies": {
4242
"@babel/core": "^7.24.4",

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [eslint-plugin-react-hooks] Add ESLint v10 support (#35720) · react/react@e8c6362 · GitHub
Skip to content

Commit e8c6362

Browse files
authored
[eslint-plugin-react-hooks] Add ESLint v10 support (#35720)
## Summary ESLint v10.0.0 was released on February 7, 2026. The current `peerDependencies` for `eslint-plugin-react-hooks` only allows up to `^9.0.0`, which causes peer dependency warnings when installing with ESLint v10. This PR: - Adds `^10.0.0` to the eslint peer dependency range - Adds `eslint-v10` to devDependencies for testing - Adds an `eslint-v10` e2e fixture (based on the existing `eslint-v9` fixture) ESLint v10's main breaking changes (removal of legacy eslintrc config, deprecated context methods) don't affect this plugin - flat config is already supported since v7.0.0, and the deprecated APIs already have fallbacks in place. ## How did you test this change? Ran the existing unit test suite: ``` cd packages/eslint-plugin-react-hooks && yarn test ``` All 5082 tests passed.
1 parent 03ca38e commit e8c6362

8 files changed

Lines changed: 265 additions & 1 deletion

File tree

‎.github/workflows/runtime_eslint_plugin_e2e.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ jobs:
2929
- "7"
3030
- "8"
3131
- "9"
32+
- "10"
3233
steps:
3334
- uses: actions/checkout@v4
3435
with:

‎fixtures/eslint-v10/README.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# ESLint v10 Fixture
2+
3+
This fixture allows us to test e2e functionality for `eslint-plugin-react-hooks` with eslint version 10.
4+
5+
Run the following to test.
6+
7+
```sh
8+
cd fixtures/eslint-v10
9+
yarn
10+
yarn build
11+
yarn lint
12+
```

‎fixtures/eslint-v10/build.mjs‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env node
2+
3+
import{execSync}from'node:child_process';
4+
import{dirname,resolve}from'node:path';
5+
import{fileURLToPath}from'node:url';
6+
7+
const__filename=fileURLToPath(import.meta.url);
8+
const__dirname=dirname(__filename);
9+
10+
execSync('yarn build -r stable eslint-plugin-react-hooks',{
11+
cwd: resolve(__dirname,'..','..'),
12+
stdio: 'inherit',
13+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import{defineConfig}from'eslint/config';
2+
importreactHooksfrom'eslint-plugin-react-hooks';
3+
4+
exportdefaultdefineConfig([
5+
reactHooks.configs.flat['recommended-latest'],
6+
{
7+
languageOptions: {
8+
ecmaVersion: 'latest',
9+
sourceType: 'module',
10+
parserOptions: {
11+
ecmaFeatures: {
12+
jsx: true,
13+
},
14+
},
15+
},
16+
rules: {
17+
'react-hooks/exhaustive-deps': 'error',
18+
},
19+
},
20+
]);

‎fixtures/eslint-v10/index.js‎

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/**
2+
* Exhaustive Deps
3+
*/
4+
// Valid because dependencies are declared correctly
5+
functionComment({comment, commentSource}){
6+
constcurrentUserID=comment.viewer.id;
7+
constenvironment=RelayEnvironment.forUser(currentUserID);
8+
constcommentID=nullthrows(comment.id);
9+
useEffect(()=>{
10+
constsubscription=SubscriptionCounter.subscribeOnce(
11+
`StoreSubscription_${commentID}`,
12+
()=>
13+
StoreSubscription.subscribe(
14+
environment,
15+
{
16+
comment_id: commentID,
17+
},
18+
currentUserID,
19+
commentSource
20+
)
21+
);
22+
return()=>subscription.dispose();
23+
},[commentID,commentSource,currentUserID,environment]);
24+
}
25+
26+
// Valid because no dependencies
27+
functionUseEffectWithNoDependencies(){
28+
constlocal={};
29+
useEffect(()=>{
30+
console.log(local);
31+
});
32+
}
33+
functionUseEffectWithEmptyDependencies(){
34+
useEffect(()=>{
35+
constlocal={};
36+
console.log(local);
37+
},[]);
38+
}
39+
40+
// OK because `props` wasn't defined.
41+
functionComponentWithNoPropsDefined(){
42+
useEffect(()=>{
43+
console.log(props.foo);
44+
},[]);
45+
}
46+
47+
// Valid because props are declared as a dependency
48+
functionComponentWithPropsDeclaredAsDep({foo}){
49+
useEffect(()=>{
50+
console.log(foo.length);
51+
console.log(foo.slice(0));
52+
},[foo]);
53+
}
54+
55+
// Valid because individual props are declared as dependencies
56+
functionComponentWithIndividualPropsDeclaredAsDeps(props){
57+
useEffect(()=>{
58+
console.log(props.foo);
59+
console.log(props.bar);
60+
},[props.bar,props.foo]);
61+
}
62+
63+
// Invalid because neither props or props.foo are declared as dependencies
64+
functionComponentWithoutDeclaringPropAsDep(props){
65+
useEffect(()=>{
66+
console.log(props.foo);
67+
// eslint-disable-next-line react-hooks/exhaustive-deps
68+
},[]);
69+
useCallback(()=>{
70+
console.log(props.foo);
71+
// eslint-disable-next-line react-hooks/exhaustive-deps
72+
},[]);
73+
// eslint-disable-next-line react-hooks/void-use-memo
74+
useMemo(()=>{
75+
console.log(props.foo);
76+
// eslint-disable-next-line react-hooks/exhaustive-deps
77+
},[]);
78+
React.useEffect(()=>{
79+
console.log(props.foo);
80+
// eslint-disable-next-line react-hooks/exhaustive-deps
81+
},[]);
82+
React.useCallback(()=>{
83+
console.log(props.foo);
84+
// eslint-disable-next-line react-hooks/exhaustive-deps
85+
},[]);
86+
// eslint-disable-next-line react-hooks/void-use-memo
87+
React.useMemo(()=>{
88+
console.log(props.foo);
89+
// eslint-disable-next-line react-hooks/exhaustive-deps
90+
},[]);
91+
React.notReactiveHook(()=>{
92+
console.log(props.foo);
93+
},[]);// This one isn't a violation
94+
}
95+
96+
/**
97+
* Rules of Hooks
98+
*/
99+
// Valid because functions can call functions.
100+
functionnormalFunctionWithConditionalFunction(){
101+
if(cond){
102+
doSomething();
103+
}
104+
}
105+
106+
// Valid because hooks can call hooks.
107+
functionuseHook(){
108+
useState();
109+
}
110+
constwhatever=functionuseHook(){
111+
useState();
112+
};
113+
constuseHook1=()=>{
114+
useState();
115+
};
116+
letuseHook2=()=>useState();
117+
useHook2=()=>{
118+
useState();
119+
};
120+
121+
// Invalid because hooks can't be called in conditionals.
122+
functionComponentWithConditionalHook(){
123+
if(cond){
124+
// eslint-disable-next-line react-hooks/rules-of-hooks
125+
useConditionalHook();
126+
}
127+
}
128+
129+
// Invalid because hooks can't be called in loops.
130+
functionuseHookInLoops(){
131+
while(a){
132+
// eslint-disable-next-line react-hooks/rules-of-hooks
133+
useHook1();
134+
if(b)return;
135+
// eslint-disable-next-line react-hooks/rules-of-hooks
136+
useHook2();
137+
}
138+
while(c){
139+
// eslint-disable-next-line react-hooks/rules-of-hooks
140+
useHook3();
141+
if(d)return;
142+
// eslint-disable-next-line react-hooks/rules-of-hooks
143+
useHook4();
144+
}
145+
}
146+
147+
/**
148+
* Compiler Rules
149+
*/
150+
// Invalid: component factory
151+
functionInvalidComponentFactory(){
152+
constDynamicComponent=()=><div>Hello</div>;
153+
// eslint-disable-next-line react-hooks/static-components
154+
return<DynamicComponent/>;
155+
}
156+
157+
// Invalid: mutating globals
158+
functionInvalidGlobals(){
159+
// eslint-disable-next-line react-hooks/immutability
160+
window.myGlobal=42;
161+
return<div>Done</div>;
162+
}
163+
164+
// Invalid: useMemo with wrong deps
165+
functionInvalidUseMemo({items}){
166+
// eslint-disable-next-line react-hooks/exhaustive-deps
167+
constsorted=useMemo(()=>[...items].sort(),[]);
168+
return<div>{sorted.length}</div>;
169+
}
170+
171+
// Invalid: missing/extra deps in useEffect
172+
functionInvalidEffectDeps({a, b}){
173+
useEffect(()=>{
174+
console.log(a);
175+
// eslint-disable-next-line react-hooks/exhaustive-deps
176+
},[]);
177+
178+
useEffect(()=>{
179+
console.log(a);
180+
// TODO: eslint-disable-next-line react-hooks/exhaustive-effect-dependencies
181+
},[a,b]);
182+
}

‎fixtures/eslint-v10/package.json‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"private": true,
3+
"name": "eslint-v10",
4+
"dependencies": {
5+
"eslint": "^10.0.0",
6+
"eslint-plugin-react-hooks": "link:../../build/oss-stable/eslint-plugin-react-hooks",
7+
"jiti": "^2.4.2"
8+
},
9+
"scripts": {
10+
"build": "node build.mjs && yarn",
11+
"lint": "tsc --noEmit && eslint index.js --report-unused-disable-directives"
12+
},
13+
"devDependencies": {
14+
"typescript": "^5.4.3"
15+
}
16+
}

‎fixtures/eslint-v10/tsconfig.json‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"compilerOptions": {
3+
"lib": [
4+
"es2022"
5+
],
6+
"module": "nodenext",
7+
"moduleResolution": "nodenext",
8+
"target": "es2022",
9+
"typeRoots": [
10+
"./node_modules/@types"
11+
],
12+
"skipLibCheck": true
13+
},
14+
"exclude": [
15+
"node_modules",
16+
"**/node_modules",
17+
"../node_modules",
18+
"../../node_modules"
19+
]
20+
}

‎packages/eslint-plugin-react-hooks/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
},
3737
"homepage": "https://react.dev/",
3838
"peerDependencies": {
39-
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
39+
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0"
4040
},
4141
"dependencies": {
4242
"@babel/core": "^7.24.4",

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' [eslint-plugin-react-hooks] Add ESLint v10 support (#35720) · react/react@e8c6362 · GitHub
Skip to content

Commit e8c6362

Browse files
authored
[eslint-plugin-react-hooks] Add ESLint v10 support (#35720)
## Summary ESLint v10.0.0 was released on February 7, 2026. The current `peerDependencies` for `eslint-plugin-react-hooks` only allows up to `^9.0.0`, which causes peer dependency warnings when installing with ESLint v10. This PR: - Adds `^10.0.0` to the eslint peer dependency range - Adds `eslint-v10` to devDependencies for testing - Adds an `eslint-v10` e2e fixture (based on the existing `eslint-v9` fixture) ESLint v10's main breaking changes (removal of legacy eslintrc config, deprecated context methods) don't affect this plugin - flat config is already supported since v7.0.0, and the deprecated APIs already have fallbacks in place. ## How did you test this change? Ran the existing unit test suite: ``` cd packages/eslint-plugin-react-hooks && yarn test ``` All 5082 tests passed.
1 parent 03ca38e commit e8c6362

8 files changed

Lines changed: 265 additions & 1 deletion

File tree

‎.github/workflows/runtime_eslint_plugin_e2e.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ jobs:
2929
- "7"
3030
- "8"
3131
- "9"
32+
- "10"
3233
steps:
3334
- uses: actions/checkout@v4
3435
with:

‎fixtures/eslint-v10/README.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# ESLint v10 Fixture
2+
3+
This fixture allows us to test e2e functionality for `eslint-plugin-react-hooks` with eslint version 10.
4+
5+
Run the following to test.
6+
7+
```sh
8+
cd fixtures/eslint-v10
9+
yarn
10+
yarn build
11+
yarn lint
12+
```

‎fixtures/eslint-v10/build.mjs‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env node
2+
3+
import{execSync}from'node:child_process';
4+
import{dirname,resolve}from'node:path';
5+
import{fileURLToPath}from'node:url';
6+
7+
const__filename=fileURLToPath(import.meta.url);
8+
const__dirname=dirname(__filename);
9+
10+
execSync('yarn build -r stable eslint-plugin-react-hooks',{
11+
cwd: resolve(__dirname,'..','..'),
12+
stdio: 'inherit',
13+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import{defineConfig}from'eslint/config';
2+
importreactHooksfrom'eslint-plugin-react-hooks';
3+
4+
exportdefaultdefineConfig([
5+
reactHooks.configs.flat['recommended-latest'],
6+
{
7+
languageOptions: {
8+
ecmaVersion: 'latest',
9+
sourceType: 'module',
10+
parserOptions: {
11+
ecmaFeatures: {
12+
jsx: true,
13+
},
14+
},
15+
},
16+
rules: {
17+
'react-hooks/exhaustive-deps': 'error',
18+
},
19+
},
20+
]);

‎fixtures/eslint-v10/index.js‎

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/**
2+
* Exhaustive Deps
3+
*/
4+
// Valid because dependencies are declared correctly
5+
functionComment({comment, commentSource}){
6+
constcurrentUserID=comment.viewer.id;
7+
constenvironment=RelayEnvironment.forUser(currentUserID);
8+
constcommentID=nullthrows(comment.id);
9+
useEffect(()=>{
10+
constsubscription=SubscriptionCounter.subscribeOnce(
11+
`StoreSubscription_${commentID}`,
12+
()=>
13+
StoreSubscription.subscribe(
14+
environment,
15+
{
16+
comment_id: commentID,
17+
},
18+
currentUserID,
19+
commentSource
20+
)
21+
);
22+
return()=>subscription.dispose();
23+
},[commentID,commentSource,currentUserID,environment]);
24+
}
25+
26+
// Valid because no dependencies
27+
functionUseEffectWithNoDependencies(){
28+
constlocal={};
29+
useEffect(()=>{
30+
console.log(local);
31+
});
32+
}
33+
functionUseEffectWithEmptyDependencies(){
34+
useEffect(()=>{
35+
constlocal={};
36+
console.log(local);
37+
},[]);
38+
}
39+
40+
// OK because `props` wasn't defined.
41+
functionComponentWithNoPropsDefined(){
42+
useEffect(()=>{
43+
console.log(props.foo);
44+
},[]);
45+
}
46+
47+
// Valid because props are declared as a dependency
48+
functionComponentWithPropsDeclaredAsDep({foo}){
49+
useEffect(()=>{
50+
console.log(foo.length);
51+
console.log(foo.slice(0));
52+
},[foo]);
53+
}
54+
55+
// Valid because individual props are declared as dependencies
56+
functionComponentWithIndividualPropsDeclaredAsDeps(props){
57+
useEffect(()=>{
58+
console.log(props.foo);
59+
console.log(props.bar);
60+
},[props.bar,props.foo]);
61+
}
62+
63+
// Invalid because neither props or props.foo are declared as dependencies
64+
functionComponentWithoutDeclaringPropAsDep(props){
65+
useEffect(()=>{
66+
console.log(props.foo);
67+
// eslint-disable-next-line react-hooks/exhaustive-deps
68+
},[]);
69+
useCallback(()=>{
70+
console.log(props.foo);
71+
// eslint-disable-next-line react-hooks/exhaustive-deps
72+
},[]);
73+
// eslint-disable-next-line react-hooks/void-use-memo
74+
useMemo(()=>{
75+
console.log(props.foo);
76+
// eslint-disable-next-line react-hooks/exhaustive-deps
77+
},[]);
78+
React.useEffect(()=>{
79+
console.log(props.foo);
80+
// eslint-disable-next-line react-hooks/exhaustive-deps
81+
},[]);
82+
React.useCallback(()=>{
83+
console.log(props.foo);
84+
// eslint-disable-next-line react-hooks/exhaustive-deps
85+
},[]);
86+
// eslint-disable-next-line react-hooks/void-use-memo
87+
React.useMemo(()=>{
88+
console.log(props.foo);
89+
// eslint-disable-next-line react-hooks/exhaustive-deps
90+
},[]);
91+
React.notReactiveHook(()=>{
92+
console.log(props.foo);
93+
},[]);// This one isn't a violation
94+
}
95+
96+
/**
97+
* Rules of Hooks
98+
*/
99+
// Valid because functions can call functions.
100+
functionnormalFunctionWithConditionalFunction(){
101+
if(cond){
102+
doSomething();
103+
}
104+
}
105+
106+
// Valid because hooks can call hooks.
107+
functionuseHook(){
108+
useState();
109+
}
110+
constwhatever=functionuseHook(){
111+
useState();
112+
};
113+
constuseHook1=()=>{
114+
useState();
115+
};
116+
letuseHook2=()=>useState();
117+
useHook2=()=>{
118+
useState();
119+
};
120+
121+
// Invalid because hooks can't be called in conditionals.
122+
functionComponentWithConditionalHook(){
123+
if(cond){
124+
// eslint-disable-next-line react-hooks/rules-of-hooks
125+
useConditionalHook();
126+
}
127+
}
128+
129+
// Invalid because hooks can't be called in loops.
130+
functionuseHookInLoops(){
131+
while(a){
132+
// eslint-disable-next-line react-hooks/rules-of-hooks
133+
useHook1();
134+
if(b)return;
135+
// eslint-disable-next-line react-hooks/rules-of-hooks
136+
useHook2();
137+
}
138+
while(c){
139+
// eslint-disable-next-line react-hooks/rules-of-hooks
140+
useHook3();
141+
if(d)return;
142+
// eslint-disable-next-line react-hooks/rules-of-hooks
143+
useHook4();
144+
}
145+
}
146+
147+
/**
148+
* Compiler Rules
149+
*/
150+
// Invalid: component factory
151+
functionInvalidComponentFactory(){
152+
constDynamicComponent=()=><div>Hello</div>;
153+
// eslint-disable-next-line react-hooks/static-components
154+
return<DynamicComponent/>;
155+
}
156+
157+
// Invalid: mutating globals
158+
functionInvalidGlobals(){
159+
// eslint-disable-next-line react-hooks/immutability
160+
window.myGlobal=42;
161+
return<div>Done</div>;
162+
}
163+
164+
// Invalid: useMemo with wrong deps
165+
functionInvalidUseMemo({items}){
166+
// eslint-disable-next-line react-hooks/exhaustive-deps
167+
constsorted=useMemo(()=>[...items].sort(),[]);
168+
return<div>{sorted.length}</div>;
169+
}
170+
171+
// Invalid: missing/extra deps in useEffect
172+
functionInvalidEffectDeps({a, b}){
173+
useEffect(()=>{
174+
console.log(a);
175+
// eslint-disable-next-line react-hooks/exhaustive-deps
176+
},[]);
177+
178+
useEffect(()=>{
179+
console.log(a);
180+
// TODO: eslint-disable-next-line react-hooks/exhaustive-effect-dependencies
181+
},[a,b]);
182+
}

‎fixtures/eslint-v10/package.json‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"private": true,
3+
"name": "eslint-v10",
4+
"dependencies": {
5+
"eslint": "^10.0.0",
6+
"eslint-plugin-react-hooks": "link:../../build/oss-stable/eslint-plugin-react-hooks",
7+
"jiti": "^2.4.2"
8+
},
9+
"scripts": {
10+
"build": "node build.mjs && yarn",
11+
"lint": "tsc --noEmit && eslint index.js --report-unused-disable-directives"
12+
},
13+
"devDependencies": {
14+
"typescript": "^5.4.3"
15+
}
16+
}

‎fixtures/eslint-v10/tsconfig.json‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"compilerOptions": {
3+
"lib": [
4+
"es2022"
5+
],
6+
"module": "nodenext",
7+
"moduleResolution": "nodenext",
8+
"target": "es2022",
9+
"typeRoots": [
10+
"./node_modules/@types"
11+
],
12+
"skipLibCheck": true
13+
},
14+
"exclude": [
15+
"node_modules",
16+
"**/node_modules",
17+
"../node_modules",
18+
"../../node_modules"
19+
]
20+
}

‎packages/eslint-plugin-react-hooks/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
},
3737
"homepage": "https://react.dev/",
3838
"peerDependencies": {
39-
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
39+
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0"
4040
},
4141
"dependencies": {
4242
"@babel/core": "^7.24.4",

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [eslint-plugin-react-hooks] Add ESLint v10 support (#35720) · react/react@e8c6362 · GitHub
Skip to content

Commit e8c6362

Browse files
authored
[eslint-plugin-react-hooks] Add ESLint v10 support (#35720)
## Summary ESLint v10.0.0 was released on February 7, 2026. The current `peerDependencies` for `eslint-plugin-react-hooks` only allows up to `^9.0.0`, which causes peer dependency warnings when installing with ESLint v10. This PR: - Adds `^10.0.0` to the eslint peer dependency range - Adds `eslint-v10` to devDependencies for testing - Adds an `eslint-v10` e2e fixture (based on the existing `eslint-v9` fixture) ESLint v10's main breaking changes (removal of legacy eslintrc config, deprecated context methods) don't affect this plugin - flat config is already supported since v7.0.0, and the deprecated APIs already have fallbacks in place. ## How did you test this change? Ran the existing unit test suite: ``` cd packages/eslint-plugin-react-hooks && yarn test ``` All 5082 tests passed.
1 parent 03ca38e commit e8c6362

8 files changed

Lines changed: 265 additions & 1 deletion

File tree

‎.github/workflows/runtime_eslint_plugin_e2e.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ jobs:
2929
- "7"
3030
- "8"
3131
- "9"
32+
- "10"
3233
steps:
3334
- uses: actions/checkout@v4
3435
with:

‎fixtures/eslint-v10/README.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# ESLint v10 Fixture
2+
3+
This fixture allows us to test e2e functionality for `eslint-plugin-react-hooks` with eslint version 10.
4+
5+
Run the following to test.
6+
7+
```sh
8+
cd fixtures/eslint-v10
9+
yarn
10+
yarn build
11+
yarn lint
12+
```

‎fixtures/eslint-v10/build.mjs‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env node
2+
3+
import{execSync}from'node:child_process';
4+
import{dirname,resolve}from'node:path';
5+
import{fileURLToPath}from'node:url';
6+
7+
const__filename=fileURLToPath(import.meta.url);
8+
const__dirname=dirname(__filename);
9+
10+
execSync('yarn build -r stable eslint-plugin-react-hooks',{
11+
cwd: resolve(__dirname,'..','..'),
12+
stdio: 'inherit',
13+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import{defineConfig}from'eslint/config';
2+
importreactHooksfrom'eslint-plugin-react-hooks';
3+
4+
exportdefaultdefineConfig([
5+
reactHooks.configs.flat['recommended-latest'],
6+
{
7+
languageOptions: {
8+
ecmaVersion: 'latest',
9+
sourceType: 'module',
10+
parserOptions: {
11+
ecmaFeatures: {
12+
jsx: true,
13+
},
14+
},
15+
},
16+
rules: {
17+
'react-hooks/exhaustive-deps': 'error',
18+
},
19+
},
20+
]);

‎fixtures/eslint-v10/index.js‎

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/**
2+
* Exhaustive Deps
3+
*/
4+
// Valid because dependencies are declared correctly
5+
functionComment({comment, commentSource}){
6+
constcurrentUserID=comment.viewer.id;
7+
constenvironment=RelayEnvironment.forUser(currentUserID);
8+
constcommentID=nullthrows(comment.id);
9+
useEffect(()=>{
10+
constsubscription=SubscriptionCounter.subscribeOnce(
11+
`StoreSubscription_${commentID}`,
12+
()=>
13+
StoreSubscription.subscribe(
14+
environment,
15+
{
16+
comment_id: commentID,
17+
},
18+
currentUserID,
19+
commentSource
20+
)
21+
);
22+
return()=>subscription.dispose();
23+
},[commentID,commentSource,currentUserID,environment]);
24+
}
25+
26+
// Valid because no dependencies
27+
functionUseEffectWithNoDependencies(){
28+
constlocal={};
29+
useEffect(()=>{
30+
console.log(local);
31+
});
32+
}
33+
functionUseEffectWithEmptyDependencies(){
34+
useEffect(()=>{
35+
constlocal={};
36+
console.log(local);
37+
},[]);
38+
}
39+
40+
// OK because `props` wasn't defined.
41+
functionComponentWithNoPropsDefined(){
42+
useEffect(()=>{
43+
console.log(props.foo);
44+
},[]);
45+
}
46+
47+
// Valid because props are declared as a dependency
48+
functionComponentWithPropsDeclaredAsDep({foo}){
49+
useEffect(()=>{
50+
console.log(foo.length);
51+
console.log(foo.slice(0));
52+
},[foo]);
53+
}
54+
55+
// Valid because individual props are declared as dependencies
56+
functionComponentWithIndividualPropsDeclaredAsDeps(props){
57+
useEffect(()=>{
58+
console.log(props.foo);
59+
console.log(props.bar);
60+
},[props.bar,props.foo]);
61+
}
62+
63+
// Invalid because neither props or props.foo are declared as dependencies
64+
functionComponentWithoutDeclaringPropAsDep(props){
65+
useEffect(()=>{
66+
console.log(props.foo);
67+
// eslint-disable-next-line react-hooks/exhaustive-deps
68+
},[]);
69+
useCallback(()=>{
70+
console.log(props.foo);
71+
// eslint-disable-next-line react-hooks/exhaustive-deps
72+
},[]);
73+
// eslint-disable-next-line react-hooks/void-use-memo
74+
useMemo(()=>{
75+
console.log(props.foo);
76+
// eslint-disable-next-line react-hooks/exhaustive-deps
77+
},[]);
78+
React.useEffect(()=>{
79+
console.log(props.foo);
80+
// eslint-disable-next-line react-hooks/exhaustive-deps
81+
},[]);
82+
React.useCallback(()=>{
83+
console.log(props.foo);
84+
// eslint-disable-next-line react-hooks/exhaustive-deps
85+
},[]);
86+
// eslint-disable-next-line react-hooks/void-use-memo
87+
React.useMemo(()=>{
88+
console.log(props.foo);
89+
// eslint-disable-next-line react-hooks/exhaustive-deps
90+
},[]);
91+
React.notReactiveHook(()=>{
92+
console.log(props.foo);
93+
},[]);// This one isn't a violation
94+
}
95+
96+
/**
97+
* Rules of Hooks
98+
*/
99+
// Valid because functions can call functions.
100+
functionnormalFunctionWithConditionalFunction(){
101+
if(cond){
102+
doSomething();
103+
}
104+
}
105+
106+
// Valid because hooks can call hooks.
107+
functionuseHook(){
108+
useState();
109+
}
110+
constwhatever=functionuseHook(){
111+
useState();
112+
};
113+
constuseHook1=()=>{
114+
useState();
115+
};
116+
letuseHook2=()=>useState();
117+
useHook2=()=>{
118+
useState();
119+
};
120+
121+
// Invalid because hooks can't be called in conditionals.
122+
functionComponentWithConditionalHook(){
123+
if(cond){
124+
// eslint-disable-next-line react-hooks/rules-of-hooks
125+
useConditionalHook();
126+
}
127+
}
128+
129+
// Invalid because hooks can't be called in loops.
130+
functionuseHookInLoops(){
131+
while(a){
132+
// eslint-disable-next-line react-hooks/rules-of-hooks
133+
useHook1();
134+
if(b)return;
135+
// eslint-disable-next-line react-hooks/rules-of-hooks
136+
useHook2();
137+
}
138+
while(c){
139+
// eslint-disable-next-line react-hooks/rules-of-hooks
140+
useHook3();
141+
if(d)return;
142+
// eslint-disable-next-line react-hooks/rules-of-hooks
143+
useHook4();
144+
}
145+
}
146+
147+
/**
148+
* Compiler Rules
149+
*/
150+
// Invalid: component factory
151+
functionInvalidComponentFactory(){
152+
constDynamicComponent=()=><div>Hello</div>;
153+
// eslint-disable-next-line react-hooks/static-components
154+
return<DynamicComponent/>;
155+
}
156+
157+
// Invalid: mutating globals
158+
functionInvalidGlobals(){
159+
// eslint-disable-next-line react-hooks/immutability
160+
window.myGlobal=42;
161+
return<div>Done</div>;
162+
}
163+
164+
// Invalid: useMemo with wrong deps
165+
functionInvalidUseMemo({items}){
166+
// eslint-disable-next-line react-hooks/exhaustive-deps
167+
constsorted=useMemo(()=>[...items].sort(),[]);
168+
return<div>{sorted.length}</div>;
169+
}
170+
171+
// Invalid: missing/extra deps in useEffect
172+
functionInvalidEffectDeps({a, b}){
173+
useEffect(()=>{
174+
console.log(a);
175+
// eslint-disable-next-line react-hooks/exhaustive-deps
176+
},[]);
177+
178+
useEffect(()=>{
179+
console.log(a);
180+
// TODO: eslint-disable-next-line react-hooks/exhaustive-effect-dependencies
181+
},[a,b]);
182+
}

‎fixtures/eslint-v10/package.json‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"private": true,
3+
"name": "eslint-v10",
4+
"dependencies": {
5+
"eslint": "^10.0.0",
6+
"eslint-plugin-react-hooks": "link:../../build/oss-stable/eslint-plugin-react-hooks",
7+
"jiti": "^2.4.2"
8+
},
9+
"scripts": {
10+
"build": "node build.mjs && yarn",
11+
"lint": "tsc --noEmit && eslint index.js --report-unused-disable-directives"
12+
},
13+
"devDependencies": {
14+
"typescript": "^5.4.3"
15+
}
16+
}

‎fixtures/eslint-v10/tsconfig.json‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"compilerOptions": {
3+
"lib": [
4+
"es2022"
5+
],
6+
"module": "nodenext",
7+
"moduleResolution": "nodenext",
8+
"target": "es2022",
9+
"typeRoots": [
10+
"./node_modules/@types"
11+
],
12+
"skipLibCheck": true
13+
},
14+
"exclude": [
15+
"node_modules",
16+
"**/node_modules",
17+
"../node_modules",
18+
"../../node_modules"
19+
]
20+
}

‎packages/eslint-plugin-react-hooks/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
},
3737
"homepage": "https://react.dev/",
3838
"peerDependencies": {
39-
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
39+
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0"
4040
},
4141
"dependencies": {
4242
"@babel/core": "^7.24.4",

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [eslint-plugin-react-hooks] Add ESLint v10 support (#35720) · react/react@e8c6362 · GitHub
Skip to content

Commit e8c6362

Browse files
authored
[eslint-plugin-react-hooks] Add ESLint v10 support (#35720)
## Summary ESLint v10.0.0 was released on February 7, 2026. The current `peerDependencies` for `eslint-plugin-react-hooks` only allows up to `^9.0.0`, which causes peer dependency warnings when installing with ESLint v10. This PR: - Adds `^10.0.0` to the eslint peer dependency range - Adds `eslint-v10` to devDependencies for testing - Adds an `eslint-v10` e2e fixture (based on the existing `eslint-v9` fixture) ESLint v10's main breaking changes (removal of legacy eslintrc config, deprecated context methods) don't affect this plugin - flat config is already supported since v7.0.0, and the deprecated APIs already have fallbacks in place. ## How did you test this change? Ran the existing unit test suite: ``` cd packages/eslint-plugin-react-hooks && yarn test ``` All 5082 tests passed.
1 parent 03ca38e commit e8c6362

8 files changed

Lines changed: 265 additions & 1 deletion

File tree

‎.github/workflows/runtime_eslint_plugin_e2e.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ jobs:
2929
- "7"
3030
- "8"
3131
- "9"
32+
- "10"
3233
steps:
3334
- uses: actions/checkout@v4
3435
with:

‎fixtures/eslint-v10/README.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# ESLint v10 Fixture
2+
3+
This fixture allows us to test e2e functionality for `eslint-plugin-react-hooks` with eslint version 10.
4+
5+
Run the following to test.
6+
7+
```sh
8+
cd fixtures/eslint-v10
9+
yarn
10+
yarn build
11+
yarn lint
12+
```

‎fixtures/eslint-v10/build.mjs‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env node
2+
3+
import{execSync}from'node:child_process';
4+
import{dirname,resolve}from'node:path';
5+
import{fileURLToPath}from'node:url';
6+
7+
const__filename=fileURLToPath(import.meta.url);
8+
const__dirname=dirname(__filename);
9+
10+
execSync('yarn build -r stable eslint-plugin-react-hooks',{
11+
cwd: resolve(__dirname,'..','..'),
12+
stdio: 'inherit',
13+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import{defineConfig}from'eslint/config';
2+
importreactHooksfrom'eslint-plugin-react-hooks';
3+
4+
exportdefaultdefineConfig([
5+
reactHooks.configs.flat['recommended-latest'],
6+
{
7+
languageOptions: {
8+
ecmaVersion: 'latest',
9+
sourceType: 'module',
10+
parserOptions: {
11+
ecmaFeatures: {
12+
jsx: true,
13+
},
14+
},
15+
},
16+
rules: {
17+
'react-hooks/exhaustive-deps': 'error',
18+
},
19+
},
20+
]);

‎fixtures/eslint-v10/index.js‎

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/**
2+
* Exhaustive Deps
3+
*/
4+
// Valid because dependencies are declared correctly
5+
functionComment({comment, commentSource}){
6+
constcurrentUserID=comment.viewer.id;
7+
constenvironment=RelayEnvironment.forUser(currentUserID);
8+
constcommentID=nullthrows(comment.id);
9+
useEffect(()=>{
10+
constsubscription=SubscriptionCounter.subscribeOnce(
11+
`StoreSubscription_${commentID}`,
12+
()=>
13+
StoreSubscription.subscribe(
14+
environment,
15+
{
16+
comment_id: commentID,
17+
},
18+
currentUserID,
19+
commentSource
20+
)
21+
);
22+
return()=>subscription.dispose();
23+
},[commentID,commentSource,currentUserID,environment]);
24+
}
25+
26+
// Valid because no dependencies
27+
functionUseEffectWithNoDependencies(){
28+
constlocal={};
29+
useEffect(()=>{
30+
console.log(local);
31+
});
32+
}
33+
functionUseEffectWithEmptyDependencies(){
34+
useEffect(()=>{
35+
constlocal={};
36+
console.log(local);
37+
},[]);
38+
}
39+
40+
// OK because `props` wasn't defined.
41+
functionComponentWithNoPropsDefined(){
42+
useEffect(()=>{
43+
console.log(props.foo);
44+
},[]);
45+
}
46+
47+
// Valid because props are declared as a dependency
48+
functionComponentWithPropsDeclaredAsDep({foo}){
49+
useEffect(()=>{
50+
console.log(foo.length);
51+
console.log(foo.slice(0));
52+
},[foo]);
53+
}
54+
55+
// Valid because individual props are declared as dependencies
56+
functionComponentWithIndividualPropsDeclaredAsDeps(props){
57+
useEffect(()=>{
58+
console.log(props.foo);
59+
console.log(props.bar);
60+
},[props.bar,props.foo]);
61+
}
62+
63+
// Invalid because neither props or props.foo are declared as dependencies
64+
functionComponentWithoutDeclaringPropAsDep(props){
65+
useEffect(()=>{
66+
console.log(props.foo);
67+
// eslint-disable-next-line react-hooks/exhaustive-deps
68+
},[]);
69+
useCallback(()=>{
70+
console.log(props.foo);
71+
// eslint-disable-next-line react-hooks/exhaustive-deps
72+
},[]);
73+
// eslint-disable-next-line react-hooks/void-use-memo
74+
useMemo(()=>{
75+
console.log(props.foo);
76+
// eslint-disable-next-line react-hooks/exhaustive-deps
77+
},[]);
78+
React.useEffect(()=>{
79+
console.log(props.foo);
80+
// eslint-disable-next-line react-hooks/exhaustive-deps
81+
},[]);
82+
React.useCallback(()=>{
83+
console.log(props.foo);
84+
// eslint-disable-next-line react-hooks/exhaustive-deps
85+
},[]);
86+
// eslint-disable-next-line react-hooks/void-use-memo
87+
React.useMemo(()=>{
88+
console.log(props.foo);
89+
// eslint-disable-next-line react-hooks/exhaustive-deps
90+
},[]);
91+
React.notReactiveHook(()=>{
92+
console.log(props.foo);
93+
},[]);// This one isn't a violation
94+
}
95+
96+
/**
97+
* Rules of Hooks
98+
*/
99+
// Valid because functions can call functions.
100+
functionnormalFunctionWithConditionalFunction(){
101+
if(cond){
102+
doSomething();
103+
}
104+
}
105+
106+
// Valid because hooks can call hooks.
107+
functionuseHook(){
108+
useState();
109+
}
110+
constwhatever=functionuseHook(){
111+
useState();
112+
};
113+
constuseHook1=()=>{
114+
useState();
115+
};
116+
letuseHook2=()=>useState();
117+
useHook2=()=>{
118+
useState();
119+
};
120+
121+
// Invalid because hooks can't be called in conditionals.
122+
functionComponentWithConditionalHook(){
123+
if(cond){
124+
// eslint-disable-next-line react-hooks/rules-of-hooks
125+
useConditionalHook();
126+
}
127+
}
128+
129+
// Invalid because hooks can't be called in loops.
130+
functionuseHookInLoops(){
131+
while(a){
132+
// eslint-disable-next-line react-hooks/rules-of-hooks
133+
useHook1();
134+
if(b)return;
135+
// eslint-disable-next-line react-hooks/rules-of-hooks
136+
useHook2();
137+
}
138+
while(c){
139+
// eslint-disable-next-line react-hooks/rules-of-hooks
140+
useHook3();
141+
if(d)return;
142+
// eslint-disable-next-line react-hooks/rules-of-hooks
143+
useHook4();
144+
}
145+
}
146+
147+
/**
148+
* Compiler Rules
149+
*/
150+
// Invalid: component factory
151+
functionInvalidComponentFactory(){
152+
constDynamicComponent=()=><div>Hello</div>;
153+
// eslint-disable-next-line react-hooks/static-components
154+
return<DynamicComponent/>;
155+
}
156+
157+
// Invalid: mutating globals
158+
functionInvalidGlobals(){
159+
// eslint-disable-next-line react-hooks/immutability
160+
window.myGlobal=42;
161+
return<div>Done</div>;
162+
}
163+
164+
// Invalid: useMemo with wrong deps
165+
functionInvalidUseMemo({items}){
166+
// eslint-disable-next-line react-hooks/exhaustive-deps
167+
constsorted=useMemo(()=>[...items].sort(),[]);
168+
return<div>{sorted.length}</div>;
169+
}
170+
171+
// Invalid: missing/extra deps in useEffect
172+
functionInvalidEffectDeps({a, b}){
173+
useEffect(()=>{
174+
console.log(a);
175+
// eslint-disable-next-line react-hooks/exhaustive-deps
176+
},[]);
177+
178+
useEffect(()=>{
179+
console.log(a);
180+
// TODO: eslint-disable-next-line react-hooks/exhaustive-effect-dependencies
181+
},[a,b]);
182+
}

‎fixtures/eslint-v10/package.json‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"private": true,
3+
"name": "eslint-v10",
4+
"dependencies": {
5+
"eslint": "^10.0.0",
6+
"eslint-plugin-react-hooks": "link:../../build/oss-stable/eslint-plugin-react-hooks",
7+
"jiti": "^2.4.2"
8+
},
9+
"scripts": {
10+
"build": "node build.mjs && yarn",
11+
"lint": "tsc --noEmit && eslint index.js --report-unused-disable-directives"
12+
},
13+
"devDependencies": {
14+
"typescript": "^5.4.3"
15+
}
16+
}

‎fixtures/eslint-v10/tsconfig.json‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"compilerOptions": {
3+
"lib": [
4+
"es2022"
5+
],
6+
"module": "nodenext",
7+
"moduleResolution": "nodenext",
8+
"target": "es2022",
9+
"typeRoots": [
10+
"./node_modules/@types"
11+
],
12+
"skipLibCheck": true
13+
},
14+
"exclude": [
15+
"node_modules",
16+
"**/node_modules",
17+
"../node_modules",
18+
"../../node_modules"
19+
]
20+
}

‎packages/eslint-plugin-react-hooks/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
},
3737
"homepage": "https://react.dev/",
3838
"peerDependencies": {
39-
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
39+
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0"
4040
},
4141
"dependencies": {
4242
"@babel/core": "^7.24.4",

0 commit comments

Comments
 (0)
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); [eslint-plugin-react-hooks] Add ESLint v10 support (#35720) · react/react@e8c6362 · GitHub
Skip to content

Commit e8c6362

Browse files
authored
[eslint-plugin-react-hooks] Add ESLint v10 support (#35720)
## Summary ESLint v10.0.0 was released on February 7, 2026. The current `peerDependencies` for `eslint-plugin-react-hooks` only allows up to `^9.0.0`, which causes peer dependency warnings when installing with ESLint v10. This PR: - Adds `^10.0.0` to the eslint peer dependency range - Adds `eslint-v10` to devDependencies for testing - Adds an `eslint-v10` e2e fixture (based on the existing `eslint-v9` fixture) ESLint v10's main breaking changes (removal of legacy eslintrc config, deprecated context methods) don't affect this plugin - flat config is already supported since v7.0.0, and the deprecated APIs already have fallbacks in place. ## How did you test this change? Ran the existing unit test suite: ``` cd packages/eslint-plugin-react-hooks && yarn test ``` All 5082 tests passed.
1 parent 03ca38e commit e8c6362

8 files changed

Lines changed: 265 additions & 1 deletion

File tree

‎.github/workflows/runtime_eslint_plugin_e2e.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ jobs:
2929
- "7"
3030
- "8"
3131
- "9"
32+
- "10"
3233
steps:
3334
- uses: actions/checkout@v4
3435
with:

‎fixtures/eslint-v10/README.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# ESLint v10 Fixture
2+
3+
This fixture allows us to test e2e functionality for `eslint-plugin-react-hooks` with eslint version 10.
4+
5+
Run the following to test.
6+
7+
```sh
8+
cd fixtures/eslint-v10
9+
yarn
10+
yarn build
11+
yarn lint
12+
```

‎fixtures/eslint-v10/build.mjs‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env node
2+
3+
import{execSync}from'node:child_process';
4+
import{dirname,resolve}from'node:path';
5+
import{fileURLToPath}from'node:url';
6+
7+
const__filename=fileURLToPath(import.meta.url);
8+
const__dirname=dirname(__filename);
9+
10+
execSync('yarn build -r stable eslint-plugin-react-hooks',{
11+
cwd: resolve(__dirname,'..','..'),
12+
stdio: 'inherit',
13+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import{defineConfig}from'eslint/config';
2+
importreactHooksfrom'eslint-plugin-react-hooks';
3+
4+
exportdefaultdefineConfig([
5+
reactHooks.configs.flat['recommended-latest'],
6+
{
7+
languageOptions: {
8+
ecmaVersion: 'latest',
9+
sourceType: 'module',
10+
parserOptions: {
11+
ecmaFeatures: {
12+
jsx: true,
13+
},
14+
},
15+
},
16+
rules: {
17+
'react-hooks/exhaustive-deps': 'error',
18+
},
19+
},
20+
]);

‎fixtures/eslint-v10/index.js‎

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/**
2+
* Exhaustive Deps
3+
*/
4+
// Valid because dependencies are declared correctly
5+
functionComment({comment, commentSource}){
6+
constcurrentUserID=comment.viewer.id;
7+
constenvironment=RelayEnvironment.forUser(currentUserID);
8+
constcommentID=nullthrows(comment.id);
9+
useEffect(()=>{
10+
constsubscription=SubscriptionCounter.subscribeOnce(
11+
`StoreSubscription_${commentID}`,
12+
()=>
13+
StoreSubscription.subscribe(
14+
environment,
15+
{
16+
comment_id: commentID,
17+
},
18+
currentUserID,
19+
commentSource
20+
)
21+
);
22+
return()=>subscription.dispose();
23+
},[commentID,commentSource,currentUserID,environment]);
24+
}
25+
26+
// Valid because no dependencies
27+
functionUseEffectWithNoDependencies(){
28+
constlocal={};
29+
useEffect(()=>{
30+
console.log(local);
31+
});
32+
}
33+
functionUseEffectWithEmptyDependencies(){
34+
useEffect(()=>{
35+
constlocal={};
36+
console.log(local);
37+
},[]);
38+
}
39+
40+
// OK because `props` wasn't defined.
41+
functionComponentWithNoPropsDefined(){
42+
useEffect(()=>{
43+
console.log(props.foo);
44+
},[]);
45+
}
46+
47+
// Valid because props are declared as a dependency
48+
functionComponentWithPropsDeclaredAsDep({foo}){
49+
useEffect(()=>{
50+
console.log(foo.length);
51+
console.log(foo.slice(0));
52+
},[foo]);
53+
}
54+
55+
// Valid because individual props are declared as dependencies
56+
functionComponentWithIndividualPropsDeclaredAsDeps(props){
57+
useEffect(()=>{
58+
console.log(props.foo);
59+
console.log(props.bar);
60+
},[props.bar,props.foo]);
61+
}
62+
63+
// Invalid because neither props or props.foo are declared as dependencies
64+
functionComponentWithoutDeclaringPropAsDep(props){
65+
useEffect(()=>{
66+
console.log(props.foo);
67+
// eslint-disable-next-line react-hooks/exhaustive-deps
68+
},[]);
69+
useCallback(()=>{
70+
console.log(props.foo);
71+
// eslint-disable-next-line react-hooks/exhaustive-deps
72+
},[]);
73+
// eslint-disable-next-line react-hooks/void-use-memo
74+
useMemo(()=>{
75+
console.log(props.foo);
76+
// eslint-disable-next-line react-hooks/exhaustive-deps
77+
},[]);
78+
React.useEffect(()=>{
79+
console.log(props.foo);
80+
// eslint-disable-next-line react-hooks/exhaustive-deps
81+
},[]);
82+
React.useCallback(()=>{
83+
console.log(props.foo);
84+
// eslint-disable-next-line react-hooks/exhaustive-deps
85+
},[]);
86+
// eslint-disable-next-line react-hooks/void-use-memo
87+
React.useMemo(()=>{
88+
console.log(props.foo);
89+
// eslint-disable-next-line react-hooks/exhaustive-deps
90+
},[]);
91+
React.notReactiveHook(()=>{
92+
console.log(props.foo);
93+
},[]);// This one isn't a violation
94+
}
95+
96+
/**
97+
* Rules of Hooks
98+
*/
99+
// Valid because functions can call functions.
100+
functionnormalFunctionWithConditionalFunction(){
101+
if(cond){
102+
doSomething();
103+
}
104+
}
105+
106+
// Valid because hooks can call hooks.
107+
functionuseHook(){
108+
useState();
109+
}
110+
constwhatever=functionuseHook(){
111+
useState();
112+
};
113+
constuseHook1=()=>{
114+
useState();
115+
};
116+
letuseHook2=()=>useState();
117+
useHook2=()=>{
118+
useState();
119+
};
120+
121+
// Invalid because hooks can't be called in conditionals.
122+
functionComponentWithConditionalHook(){
123+
if(cond){
124+
// eslint-disable-next-line react-hooks/rules-of-hooks
125+
useConditionalHook();
126+
}
127+
}
128+
129+
// Invalid because hooks can't be called in loops.
130+
functionuseHookInLoops(){
131+
while(a){
132+
// eslint-disable-next-line react-hooks/rules-of-hooks
133+
useHook1();
134+
if(b)return;
135+
// eslint-disable-next-line react-hooks/rules-of-hooks
136+
useHook2();
137+
}
138+
while(c){
139+
// eslint-disable-next-line react-hooks/rules-of-hooks
140+
useHook3();
141+
if(d)return;
142+
// eslint-disable-next-line react-hooks/rules-of-hooks
143+
useHook4();
144+
}
145+
}
146+
147+
/**
148+
* Compiler Rules
149+
*/
150+
// Invalid: component factory
151+
functionInvalidComponentFactory(){
152+
constDynamicComponent=()=><div>Hello</div>;
153+
// eslint-disable-next-line react-hooks/static-components
154+
return<DynamicComponent/>;
155+
}
156+
157+
// Invalid: mutating globals
158+
functionInvalidGlobals(){
159+
// eslint-disable-next-line react-hooks/immutability
160+
window.myGlobal=42;
161+
return<div>Done</div>;
162+
}
163+
164+
// Invalid: useMemo with wrong deps
165+
functionInvalidUseMemo({items}){
166+
// eslint-disable-next-line react-hooks/exhaustive-deps
167+
constsorted=useMemo(()=>[...items].sort(),[]);
168+
return<div>{sorted.length}</div>;
169+
}
170+
171+
// Invalid: missing/extra deps in useEffect
172+
functionInvalidEffectDeps({a, b}){
173+
useEffect(()=>{
174+
console.log(a);
175+
// eslint-disable-next-line react-hooks/exhaustive-deps
176+
},[]);
177+
178+
useEffect(()=>{
179+
console.log(a);
180+
// TODO: eslint-disable-next-line react-hooks/exhaustive-effect-dependencies
181+
},[a,b]);
182+
}

‎fixtures/eslint-v10/package.json‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"private": true,
3+
"name": "eslint-v10",
4+
"dependencies": {
5+
"eslint": "^10.0.0",
6+
"eslint-plugin-react-hooks": "link:../../build/oss-stable/eslint-plugin-react-hooks",
7+
"jiti": "^2.4.2"
8+
},
9+
"scripts": {
10+
"build": "node build.mjs && yarn",
11+
"lint": "tsc --noEmit && eslint index.js --report-unused-disable-directives"
12+
},
13+
"devDependencies": {
14+
"typescript": "^5.4.3"
15+
}
16+
}

‎fixtures/eslint-v10/tsconfig.json‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"compilerOptions": {
3+
"lib": [
4+
"es2022"
5+
],
6+
"module": "nodenext",
7+
"moduleResolution": "nodenext",
8+
"target": "es2022",
9+
"typeRoots": [
10+
"./node_modules/@types"
11+
],
12+
"skipLibCheck": true
13+
},
14+
"exclude": [
15+
"node_modules",
16+
"**/node_modules",
17+
"../node_modules",
18+
"../../node_modules"
19+
]
20+
}

‎packages/eslint-plugin-react-hooks/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
},
3737
"homepage": "https://react.dev/",
3838
"peerDependencies": {
39-
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
39+
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0"
4040
},
4141
"dependencies": {
4242
"@babel/core": "^7.24.4",

0 commit comments

Comments
 (0)