Skip to content

Commit 069d23b

Browse files
authored
[eslint-plugin-exhaustive-deps] Fix exhaustive deps check for unstable vars (#24343)
* Fix exhaustive deps for unstable vars * Fix formatting * Optimise iterations * Fix linting
1 parent 4997515 commit 069d23b

File tree

2 files changed

+68
-3
lines changed

2 files changed

+68
-3
lines changed

packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js

+58
Original file line numberDiff line numberDiff line change
@@ -1095,6 +1095,22 @@ const tests = {
10951095
}
10961096
`,
10971097
},
1098+
{
1099+
code: normalizeIndent`
1100+
function Counter(unstableProp) {
1101+
let [count, setCount] = useState(0);
1102+
setCount = unstableProp
1103+
useEffect(() => {
1104+
let id = setInterval(() => {
1105+
setCount(c => c + 1);
1106+
}, 1000);
1107+
return () => clearInterval(id);
1108+
}, [setCount]);
1109+
1110+
return <h1>{count}</h1>;
1111+
}
1112+
`,
1113+
},
10981114
{
10991115
code: normalizeIndent`
11001116
function Counter() {
@@ -1581,6 +1597,48 @@ const tests = {
15811597
},
15821598
],
15831599
},
1600+
{
1601+
code: normalizeIndent`
1602+
function Counter(unstableProp) {
1603+
let [count, setCount] = useState(0);
1604+
setCount = unstableProp
1605+
useEffect(() => {
1606+
let id = setInterval(() => {
1607+
setCount(c => c + 1);
1608+
}, 1000);
1609+
return () => clearInterval(id);
1610+
}, []);
1611+
1612+
return <h1>{count}</h1>;
1613+
}
1614+
`,
1615+
errors: [
1616+
{
1617+
message:
1618+
"React Hook useEffect has a missing dependency: 'setCount'. " +
1619+
'Either include it or remove the dependency array.',
1620+
suggestions: [
1621+
{
1622+
desc: 'Update the dependencies array to be: [setCount]',
1623+
output: normalizeIndent`
1624+
function Counter(unstableProp) {
1625+
let [count, setCount] = useState(0);
1626+
setCount = unstableProp
1627+
useEffect(() => {
1628+
let id = setInterval(() => {
1629+
setCount(c => c + 1);
1630+
}, 1000);
1631+
return () => clearInterval(id);
1632+
}, [setCount]);
1633+
1634+
return <h1>{count}</h1>;
1635+
}
1636+
`,
1637+
},
1638+
],
1639+
},
1640+
],
1641+
},
15841642
{
15851643
// Note: we *could* detect it's a primitive and never assigned
15861644
// even though it's not a constant -- but we currently don't.

packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,14 @@ export default {
234234
if (id.elements[1] === resolved.identifiers[0]) {
235235
if (name === 'useState') {
236236
const references = resolved.references;
237+
let writeCount = 0;
237238
for (let i = 0; i < references.length; i++) {
239+
if (references[i].isWrite()) {
240+
writeCount++;
241+
}
242+
if (writeCount > 1) {
243+
return false;
244+
}
238245
setStateCallSites.set(
239246
references[i].identifier,
240247
id.elements[0],
@@ -321,7 +328,7 @@ export default {
321328
pureScopes.has(ref.resolved.scope) &&
322329
// Stable values are fine though,
323330
// although we won't check functions deeper.
324-
!memoizedIsStablecKnownHookValue(ref.resolved)
331+
!memoizedIsStableKnownHookValue(ref.resolved)
325332
) {
326333
return false;
327334
}
@@ -332,7 +339,7 @@ export default {
332339
}
333340

334341
// Remember such values. Avoid re-running extra checks on them.
335-
const memoizedIsStablecKnownHookValue = memoizeWithWeakMap(
342+
const memoizedIsStableKnownHookValue = memoizeWithWeakMap(
336343
isStableKnownHookValue,
337344
stableKnownValueCache,
338345
);
@@ -435,7 +442,7 @@ export default {
435442
if (!dependencies.has(dependency)) {
436443
const resolved = reference.resolved;
437444
const isStable =
438-
memoizedIsStablecKnownHookValue(resolved) ||
445+
memoizedIsStableKnownHookValue(resolved) ||
439446
memoizedIsFunctionWithoutCapturedValues(resolved);
440447
dependencies.set(dependency, {
441448
isStable,

0 commit comments

Comments
 (0)