diff --git a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js index 8d8040bb43cd1..2f3e14c5f95e4 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js +++ b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js @@ -1430,6 +1430,72 @@ if (__EXPERIMENTAL__) { } `, }, + { + code: normalizeIndent` + // Valid because functions created with useEffectEvent can be called in useLayoutEffect. + function MyComponent({ theme }) { + const onClick = useEffectEvent(() => { + showNotification(theme); + }); + useLayoutEffect(() => { + onClick(); + }); + React.useLayoutEffect(() => { + onClick(); + }); + } + `, + }, + { + code: normalizeIndent` + // Valid because functions created with useEffectEvent can be called in useInsertionEffect. + function MyComponent({ theme }) { + const onClick = useEffectEvent(() => { + showNotification(theme); + }); + useInsertionEffect(() => { + onClick(); + }); + React.useInsertionEffect(() => { + onClick(); + }); + } + `, + }, + { + code: normalizeIndent` + // Valid because functions created with useEffectEvent can be passed by reference in useLayoutEffect + // and useInsertionEffect. + function MyComponent({ theme }) { + const onClick = useEffectEvent(() => { + showNotification(theme); + }); + const onClick2 = useEffectEvent(() => { + debounce(onClick); + debounce(() => onClick()); + debounce(() => { onClick() }); + deboucne(() => debounce(onClick)); + }); + useLayoutEffect(() => { + let id = setInterval(() => onClick(), 100); + return () => clearInterval(onClick); + }, []); + React.useLayoutEffect(() => { + let id = setInterval(() => onClick(), 100); + return () => clearInterval(onClick); + }, []); + useInsertionEffect(() => { + let id = setInterval(() => onClick(), 100); + return () => clearInterval(onClick); + }, []); + React.useInsertionEffect(() => { + let id = setInterval(() => onClick(), 100); + return () => clearInterval(onClick); + }, []); + return null; + } + `, + }, ]; allTests.invalid = [ ...allTests.invalid, diff --git a/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts b/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts index 0721a75e00642..4c7618d8e084c 100644 --- a/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts +++ b/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts @@ -147,8 +147,8 @@ function getNodeWithoutReactNamespace( return node; } -function isUseEffectIdentifier(node: Node): boolean { - return node.type === 'Identifier' && node.name === 'useEffect'; +function isEffectIdentifier(node: Node): boolean { + return node.type === 'Identifier' && (node.name === 'useEffect' || node.name === 'useLayoutEffect' || node.name === 'useInsertionEffect'); } function isUseEffectEventIdentifier(node: Node): boolean { if (__EXPERIMENTAL__) { @@ -726,7 +726,7 @@ const rule = { // Check all `useEffect` and `React.useEffect`, `useEffectEvent`, and `React.useEffectEvent` const nodeWithoutNamespace = getNodeWithoutReactNamespace(node.callee); if ( - (isUseEffectIdentifier(nodeWithoutNamespace) || + (isEffectIdentifier(nodeWithoutNamespace) || isUseEffectEventIdentifier(nodeWithoutNamespace)) && node.arguments.length > 0 ) {