[eslint-plugin-exhaustive-deps] Fix exhaustive deps check for unstable vars (#24343)
[eslint-plugin-exhaustive-deps] Fix exhaustive deps check for unstable vars (#24343) * Fix exhaustive deps for unstable vars * Fix formatting * Optimise iterations * Fix linting
Afzal Sayed committed
Apr 12, 2022 at 02:13 UTC
069d23bb74939c19bc8860ad4013324bd6e1c5f3
2 files changed
+68
-3
packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js
+58
@@ -1095,6 +1095,22 @@ const tests = {
1095
}
1096
`,
1097
},
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
+ },
1114
{
1115
code: normalizeIndent`
1116
function Counter() {
@@ -1581,6 +1597,48 @@ const tests = {
1597
},
1598
],
1599
},
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
+ },
1642
{
1643
// Note: we *could* detect it's a primitive and never assigned
1644
// even though it's not a constant -- but we currently don't.
packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js
+10
-3
@@ -234,7 +234,14 @@ export default {
234
if (id.elements[1] === resolved.identifiers[0]) {
235
if (name === 'useState') {
236
const references = resolved.references;
237
+ let writeCount = 0;
238
for (let i = 0; i < references.length; i++) {
239
+ if (references[i].isWrite()) {
240
+ writeCount++;
241
+ }
242
+ if (writeCount > 1) {
243
+ return false;
244
+ }
245
setStateCallSites.set(
246
references[i].identifier,
247
id.elements[0],
@@ -321,7 +328,7 @@ export default {
328
pureScopes.has(ref.resolved.scope) &&
329
// Stable values are fine though,
330
// although we won't check functions deeper.
324
- !memoizedIsStablecKnownHookValue(ref.resolved)
331
+ !memoizedIsStableKnownHookValue(ref.resolved)
332
) {
333
return false;
334
}
@@ -332,7 +339,7 @@ export default {
339
}
340
341
// Remember such values. Avoid re-running extra checks on them.
335
- const memoizedIsStablecKnownHookValue = memoizeWithWeakMap(
342
+ const memoizedIsStableKnownHookValue = memoizeWithWeakMap(
343
isStableKnownHookValue,
344
stableKnownValueCache,
345
);
@@ -435,7 +442,7 @@ export default {
442
if (!dependencies.has(dependency)) {
443
const resolved = reference.resolved;
444
const isStable =
438
- memoizedIsStablecKnownHookValue(resolved) ||
445
+ memoizedIsStableKnownHookValue(resolved) ||
446
memoizedIsFunctionWithoutCapturedValues(resolved);
447
dependencies.set(dependency, {
448
isStable,