diff --git a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
index 22d2427de69d3..1690f20ede490 100644
--- a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
+++ b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
@@ -1343,33 +1343,6 @@ if (__EXPERIMENTAL__) {
}
`,
},
- {
- code: normalizeIndent`
- // Valid because functions created with useEffectEvent can be called in closures.
- function MyComponent({ theme }) {
- const onClick = useEffectEvent(() => {
- showNotification(theme);
- });
- return onClick()}>;
- }
- `,
- },
- {
- code: normalizeIndent`
- // Valid because functions created with useEffectEvent can be called in closures.
- function MyComponent({ theme }) {
- const onClick = useEffectEvent(() => {
- showNotification(theme);
- });
- const onClick2 = () => { onClick() };
- const onClick3 = useCallback(() => onClick(), []);
- return <>
-
-
- >;
- }
- `,
- },
{
code: normalizeIndent`
// Valid because functions created with useEffectEvent can be passed by reference in useEffect
@@ -1380,47 +1353,39 @@ if (__EXPERIMENTAL__) {
});
const onClick2 = useEffectEvent(() => {
debounce(onClick);
+ debounce(() => onClick());
+ debounce(() => { onClick() });
+ deboucne(() => debounce(onClick));
});
useEffect(() => {
- let id = setInterval(onClick, 100);
+ let id = setInterval(() => onClick(), 100);
return () => clearInterval(onClick);
}, []);
- return onClick2()} />
+ return null;
}
`,
},
- {
- code: normalizeIndent`
- const MyComponent = ({theme}) => {
- const onClick = useEffectEvent(() => {
- showNotification(theme);
- });
- return onClick()}>;
- };
- `,
- },
{
code: normalizeIndent`
function MyComponent({ theme }) {
- const notificationService = useNotifications();
- const showNotification = useEffectEvent((text) => {
- notificationService.notify(theme, text);
+ useEffect(() => {
+ onClick();
});
- const onClick = useEffectEvent((text) => {
- showNotification(text);
+ const onClick = useEffectEvent(() => {
+ showNotification(theme);
});
- return onClick(text)} />
}
`,
},
{
code: normalizeIndent`
function MyComponent({ theme }) {
- useEffect(() => {
- onClick();
+ const onEvent = useEffectEvent((text) => {
+ console.log(text);
});
- const onClick = useEffectEvent(() => {
- showNotification(theme);
+
+ useEffect(() => {
+ onEvent('Hello world');
});
}
`,
@@ -1437,7 +1402,7 @@ if (__EXPERIMENTAL__) {
return ;
}
`,
- errors: [useEffectEventError('onClick')],
+ errors: [useEffectEventError('onClick', false)],
},
{
code: normalizeIndent`
@@ -1456,8 +1421,23 @@ if (__EXPERIMENTAL__) {
});
return onClick()} />
}
+
+ // The useEffectEvent function shares an identifier name with the above
+ function MyLastComponent({theme}) {
+ const onClick = useEffectEvent(() => {
+ showNotification(theme)
+ });
+ useEffect(() => {
+ onClick(); // No error here, errors on all other uses
+ onClick;
+ })
+ return
+ }
`,
- errors: [{...useEffectEventError('onClick'), line: 7}],
+ errors: [
+ {...useEffectEventError('onClick', false), line: 7},
+ {...useEffectEventError('onClick', true), line: 15},
+ ],
},
{
code: normalizeIndent`
@@ -1468,7 +1448,7 @@ if (__EXPERIMENTAL__) {
return ;
}
`,
- errors: [useEffectEventError('onClick')],
+ errors: [useEffectEventError('onClick', false)],
},
{
code: normalizeIndent`
@@ -1481,7 +1461,7 @@ if (__EXPERIMENTAL__) {
return
}
`,
- errors: [{...useEffectEventError('onClick'), line: 7}],
+ errors: [{...useEffectEventError('onClick', false), line: 7}],
},
{
code: normalizeIndent`
@@ -1497,7 +1477,27 @@ if (__EXPERIMENTAL__) {
return
}
`,
- errors: [useEffectEventError('onClick')],
+ errors: [useEffectEventError('onClick', false)],
+ },
+ {
+ code: normalizeIndent`
+ // Invalid because functions created with useEffectEvent cannot be called in arbitrary closures.
+ function MyComponent({ theme }) {
+ const onClick = useEffectEvent(() => {
+ showNotification(theme);
+ });
+ const onClick2 = () => { onClick() };
+ const onClick3 = useCallback(() => onClick(), []);
+ return <>
+
+
+ >;
+ }
+ `,
+ errors: [
+ useEffectEventError('onClick', true),
+ useEffectEventError('onClick', true),
+ ],
},
];
}
@@ -1559,11 +1559,11 @@ function classError(hook) {
};
}
-function useEffectEventError(fn) {
+function useEffectEventError(fn, called) {
return {
message:
`\`${fn}\` is a function created with React Hook "useEffectEvent", and can only be called from ` +
- 'the same component. They cannot be assigned to variables or passed down.',
+ `the same component.${called ? '' : ' They cannot be assigned to variables or passed down.'}`,
};
}
diff --git a/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts b/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts
index ac7d0f3a06cfe..8d42b319b4976 100644
--- a/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts
+++ b/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts
@@ -541,7 +541,9 @@ const rule = {
context.report({
node: hook,
message:
- `React Hook "${getSourceCode().getText(hook)}" may be executed ` +
+ `React Hook "${getSourceCode().getText(
+ hook,
+ )}" may be executed ` +
'more than once. Possibly because it is called in a loop. ' +
'React Hooks must be called in the exact same order in ' +
'every component render.',
@@ -596,7 +598,9 @@ const rule = {
) {
// Custom message for hooks inside a class
const message =
- `React Hook "${getSourceCode().getText(hook)}" cannot be called ` +
+ `React Hook "${getSourceCode().getText(
+ hook,
+ )}" cannot be called ` +
'in a class component. React Hooks must be called in a ' +
'React function component or a custom React Hook function.';
context.report({node: hook, message});
@@ -613,7 +617,9 @@ const rule = {
} else if (codePathNode.type === 'Program') {
// These are dangerous if you have inline requires enabled.
const message =
- `React Hook "${getSourceCode().getText(hook)}" cannot be called ` +
+ `React Hook "${getSourceCode().getText(
+ hook,
+ )}" cannot be called ` +
'at the top level. React Hooks must be called in a ' +
'React function component or a custom React Hook function.';
context.report({node: hook, message});
@@ -626,7 +632,9 @@ const rule = {
// `use(...)` can be called in callbacks.
if (isSomewhereInsideComponentOrHook && !isUseIdentifier(hook)) {
const message =
- `React Hook "${getSourceCode().getText(hook)}" cannot be called ` +
+ `React Hook "${getSourceCode().getText(
+ hook,
+ )}" cannot be called ` +
'inside a callback. React Hooks must be called in a ' +
'React function component or a custom React Hook function.';
context.report({node: hook, message});
@@ -681,18 +689,18 @@ const rule = {
Identifier(node) {
// This identifier resolves to a useEffectEvent function, but isn't being referenced in an
// effect or another event function. It isn't being called either.
- if (
- lastEffect == null &&
- useEffectEventFunctions.has(node) &&
- node.parent.type !== 'CallExpression'
- ) {
+ if (lastEffect == null && useEffectEventFunctions.has(node)) {
+ const message =
+ `\`${getSourceCode().getText(
+ node,
+ )}\` is a function created with React Hook "useEffectEvent", and can only be called from ` +
+ 'the same component.' +
+ (node.parent.type === 'CallExpression'
+ ? ''
+ : ' They cannot be assigned to variables or passed down.');
context.report({
node,
- message:
- `\`${getSourceCode().getText(
- node,
- )}\` is a function created with React Hook "useEffectEvent", and can only be called from ` +
- 'the same component. They cannot be assigned to variables or passed down.',
+ message,
});
}
},