diff --git a/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js b/packages/eslint-plugin-react-hooks/__tests__/ESLintRuleExhaustiveDeps-test.js
index 4b6b3370e8b29..5c3c64c173fa9 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 21d48141717cc..26d9688ac17c4 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,