diff --git a/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js b/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js index 4b6b3370e8b2..5c3c64c173fa 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js +++ b/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js @@ -1095,6 +1095,22 @@ const tests = { } `, }, + { + code: normalizeIndent` + function Counter(unstableProp) { + let [count, setCount] = useState(0); + setCount = unstableProp + useEffect(() => { + let id = setInterval(() => { + setCount(c => c + 1); + }, 1000); + return () => clearInterval(id); + }, [setCount]); + + return

{count}

; + } + `, + }, { code: normalizeIndent` function Counter() { @@ -1581,6 +1597,48 @@ const tests = { }, ], }, + { + code: normalizeIndent` + function Counter(unstableProp) { + let [count, setCount] = useState(0); + setCount = unstableProp + useEffect(() => { + let id = setInterval(() => { + setCount(c => c + 1); + }, 1000); + return () => clearInterval(id); + }, []); + + return

{count}

; + } + `, + errors: [ + { + message: + "React Hook useEffect has a missing dependency: 'setCount'. " + + 'Either include it or remove the dependency array.', + suggestions: [ + { + desc: 'Update the dependencies array to be: [setCount]', + output: normalizeIndent` + function Counter(unstableProp) { + let [count, setCount] = useState(0); + setCount = unstableProp + useEffect(() => { + let id = setInterval(() => { + setCount(c => c + 1); + }, 1000); + return () => clearInterval(id); + }, [setCount]); + + return

{count}

; + } + `, + }, + ], + }, + ], + }, { // Note: we *could* detect it's a primitive and never assigned // even though it's not a constant -- but we currently don't. diff --git a/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js b/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js index 21d48141717c..26d9688ac17c 100644 --- a/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js +++ b/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js @@ -234,7 +234,14 @@ export default { if (id.elements[1] === resolved.identifiers[0]) { if (name === 'useState') { const references = resolved.references; + let writeCount = 0; for (let i = 0; i < references.length; i++) { + if (references[i].isWrite()) { + writeCount++; + } + if (writeCount > 1) { + return false; + } setStateCallSites.set( references[i].identifier, id.elements[0], @@ -321,7 +328,7 @@ export default { pureScopes.has(ref.resolved.scope) && // Stable values are fine though, // although we won't check functions deeper. - !memoizedIsStablecKnownHookValue(ref.resolved) + !memoizedIsStableKnownHookValue(ref.resolved) ) { return false; } @@ -332,7 +339,7 @@ export default { } // Remember such values. Avoid re-running extra checks on them. - const memoizedIsStablecKnownHookValue = memoizeWithWeakMap( + const memoizedIsStableKnownHookValue = memoizeWithWeakMap( isStableKnownHookValue, stableKnownValueCache, ); @@ -435,7 +442,7 @@ export default { if (!dependencies.has(dependency)) { const resolved = reference.resolved; const isStable = - memoizedIsStablecKnownHookValue(resolved) || + memoizedIsStableKnownHookValue(resolved) || memoizedIsFunctionWithoutCapturedValues(resolved); dependencies.set(dependency, { isStable,