Skip to content

Commit 67f600d

Browse files
committed
[useEvent] Lint for presence of useEvent functions in dependency lists
With #25473, the identity of useEvent's return value is no longer stable across renders. Previously, the ExhaustiveDeps lint rule would only allow the omission of the useEvent function, but you could still add it as a dependency. This PR updates the ExhaustiveDeps rule to explicitly check for the presence of useEvent functions in dependency lists, and emits a warning and suggestion/autofixer for removing the dependency.
1 parent 9872928 commit 67f600d

File tree

2 files changed

+74
-10
lines changed

2 files changed

+74
-10
lines changed

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

+48-9
Original file line numberDiff line numberDiff line change
@@ -7635,15 +7635,54 @@ if (__EXPERIMENTAL__) {
76357635
...tests.valid,
76367636
{
76377637
code: normalizeIndent`
7638-
function MyComponent({ theme }) {
7639-
const onStuff = useEvent(() => {
7640-
showNotification(theme);
7641-
});
7642-
useEffect(() => {
7643-
onStuff();
7644-
}, []);
7645-
}
7646-
`,
7638+
function MyComponent({ theme }) {
7639+
const onStuff = useEvent(() => {
7640+
showNotification(theme);
7641+
});
7642+
useEffect(() => {
7643+
onStuff();
7644+
}, []);
7645+
}
7646+
`,
7647+
},
7648+
];
7649+
7650+
tests.invalid = [
7651+
...tests.invalid,
7652+
{
7653+
code: normalizeIndent`
7654+
function MyComponent({ theme }) {
7655+
const onStuff = useEvent(() => {
7656+
showNotification(theme);
7657+
});
7658+
useEffect(() => {
7659+
onStuff();
7660+
}, [onStuff]);
7661+
}
7662+
`,
7663+
errors: [
7664+
{
7665+
message:
7666+
'`useEvent` functions always return a new identity for every render. This means that ' +
7667+
'it should not be included in dependency lists, as it would cause the callback to be ' +
7668+
'run on every render. You can safely remove this.',
7669+
suggestions: [
7670+
{
7671+
desc: 'Remove the dependency `onStuff`',
7672+
output: normalizeIndent`
7673+
function MyComponent({ theme }) {
7674+
const onStuff = useEvent(() => {
7675+
showNotification(theme);
7676+
});
7677+
useEffect(() => {
7678+
onStuff();
7679+
}, []);
7680+
}
7681+
`,
7682+
},
7683+
],
7684+
},
7685+
],
76477686
},
76487687
];
76497688
}

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

+26-1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export default {
7474
const stateVariables = new WeakSet();
7575
const stableKnownValueCache = new WeakMap();
7676
const functionWithoutCapturedValueCache = new WeakMap();
77+
const useEventVariables = new WeakSet();
7778
function memoizeWithWeakMap(fn, map) {
7879
return function(arg) {
7980
if (map.has(arg)) {
@@ -226,7 +227,12 @@ export default {
226227
// useRef() return value is stable.
227228
return true;
228229
} else if (isUseEventIdentifier(callee) && id.type === 'Identifier') {
229-
// useEvent() return value is stable.
230+
for (const ref of resolved.references) {
231+
if (ref !== id) {
232+
useEventVariables.add(ref.identifier);
233+
}
234+
}
235+
// useEvent() return value is always unstable.
230236
return true;
231237
} else if (name === 'useState' || name === 'useReducer') {
232238
// Only consider second value in initializing tuple stable.
@@ -639,6 +645,25 @@ export default {
639645
});
640646
return;
641647
}
648+
if (useEventVariables.has(declaredDependencyNode)) {
649+
reportProblem({
650+
node: declaredDependencyNode,
651+
message:
652+
'`useEvent` functions always return a new identity for every render. This means ' +
653+
'that it should not be included in dependency lists, as it would cause the ' +
654+
'callback to be run on every render. You can safely remove this.',
655+
suggest: [
656+
{
657+
desc: `Remove the dependency \`${context.getSource(
658+
declaredDependencyNode,
659+
)}\``,
660+
fix(fixer) {
661+
return fixer.removeRange(declaredDependencyNode.range);
662+
},
663+
},
664+
],
665+
});
666+
}
642667
// Try to normalize the declared dependency. If we can't then an error
643668
// will be thrown. We will catch that error and report an error.
644669
let declaredDependency;

0 commit comments

Comments
 (0)