@samitouri / QOS-React / commits / 069d23bb74

[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,