Skip to content

Commit f85de77

Browse files
committed
Only subtraverse within useEffect CallExprs
1 parent 1b0db92 commit f85de77

File tree

2 files changed

+42
-7
lines changed

2 files changed

+42
-7
lines changed

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,18 @@ const tests = {
481481
return <Child onClick={() => onClick()}></Child>;
482482
};
483483
`,
484+
`
485+
function MyComponent({ theme }) {
486+
const notificationService = useNotifications();
487+
const showNotification = useEvent((text) => {
488+
notificationService.notify(theme, text);
489+
});
490+
const onClick = useEvent((text) => {
491+
showNotification(text);
492+
});
493+
return <Child onClick={(text) => onClick(text)} />
494+
}
495+
`,
484496
],
485497
invalid: [
486498
{
@@ -1115,6 +1127,22 @@ const tests = {
11151127
`,
11161128
errors: [useEventError('onClick')],
11171129
},
1130+
{
1131+
code: `
1132+
// Invalid because onClick is being aliased to foo but not invoked
1133+
function MyComponent({ theme }) {
1134+
const onClick = useEvent(() => {
1135+
showNotification(theme);
1136+
});
1137+
let foo;
1138+
useEffect(() => {
1139+
foo = onClick;
1140+
});
1141+
return <Bar onClick={foo} />
1142+
}
1143+
`,
1144+
errors: [useEventError('onClick')],
1145+
},
11181146
],
11191147
};
11201148

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -584,17 +584,24 @@ export default {
584584
const scope = context.getScope();
585585
// useEvent: Resolve a function created with useEvent that is invoked locally at least once.
586586
// OK - onClick();
587-
if (!isUseEventIdentifier(node.callee)) {
588-
resolveUseEventViolation(scope, node.callee);
589-
}
587+
resolveUseEventViolation(scope, node.callee);
590588

591-
// useEvent: useEvent functions can be passed by reference within useEffect.
589+
// useEvent: useEvent functions can be passed by reference within useEffect
592590
// OK - useEffect(() => { ...onClick... }, []);
593591
if (isUseEffectIdentifier(node.callee) && node.arguments.length > 0) {
594-
// Subtraverse here so we don't need to visit every Identifier node.
592+
// Subtraverse here so we don't need to visit every Identifier node. Conservatively, we
593+
// only resolve a useEventViolation if the useEvent function Identifier is consumed by a
594+
// CallExpression (eg setInterval(onClick, 100)), with the assumption that something else
595+
// will call the function later.
596+
//
597+
// We may want to revisit this and remove this shorthand entirely at some point.
595598
traverse(context, node, childNode => {
596-
if (childNode.type === 'Identifier') {
597-
resolveUseEventViolation(scope, childNode);
599+
if (childNode.type === 'CallExpression') {
600+
for (const argument of childNode.arguments) {
601+
if (argument.type === 'Identifier') {
602+
resolveUseEventViolation(scope, argument);
603+
}
604+
}
598605
}
599606
});
600607
}

0 commit comments

Comments
 (0)