diff --git a/crates/oxc_linter/src/rules/react/exhaustive_deps.rs b/crates/oxc_linter/src/rules/react/exhaustive_deps.rs index 3a44744812a02..61df2698ccbe7 100644 --- a/crates/oxc_linter/src/rules/react/exhaustive_deps.rs +++ b/crates/oxc_linter/src/rules/react/exhaustive_deps.rs @@ -893,7 +893,7 @@ fn is_stable_value<'a, 'b>( return false; }; - if init_name == "useRef" || init_name == "useCallback" { + if init_name == "useRef" { return true; } @@ -2009,18 +2009,20 @@ fn test() { ); }", - r"function Example() { - const foo = useCallback(() => { - foo(); - }, []); - }", - r"function Example({ prop }) { - const foo = useCallback(() => { - if (prop) { - foo(); - } - }, [prop]); - }", + // we don't support the following two cases as they would both cause an infinite loop at runtime + // r"function Example() { + // const foo = useCallback(() => { + // foo(); + // }, []); + // }", + // + // r"function Example({ prop }) { + // const foo = useCallback(() => { + // if (prop) { + // foo(); + // } + // }, [prop]); + // }", r"function Hello() { const [state, setState] = useState(0); useEffect(() => { @@ -3324,21 +3326,20 @@ fn test() { r"function Thing() { useEffect(async () => {}); }", - // TODO: not supported yet + // NOTE: intentionally not supported, as `foo` would be referenced before it's declaration // r"function Example() { // const foo = useCallback(() => { // foo(); // }, [foo]); // }", - // TODO: not supported yet - // r"function Example({ prop }) { - // const foo = useCallback(() => { - // prop.hello(foo); - // }, [foo]); - // const bar = useCallback(() => { - // foo(); - // }, [foo]); - // }", + r"function Example({ prop }) { + const foo = useCallback(() => { + prop.hello(foo); + }, [foo]); + const bar = useCallback(() => { + foo(); + }, [foo]); + }", r"function MyComponent() { const local = {}; function myEffect() { @@ -3563,6 +3564,17 @@ fn test() { <> ) }", + // https://github.com/oxc-project/oxc/issues/9788 + r#"import { useCallback, useEffect } from "react"; + + function Component({ foo }) { + const log = useCallback(() => { + console.log(foo); + }, [foo]); + useEffect(() => { + log(); + }, []); + }"#, ]; Tester::new(ExhaustiveDeps::NAME, ExhaustiveDeps::PLUGIN, pass, fail).test_and_snapshot(); diff --git a/crates/oxc_linter/src/snapshots/react_exhaustive_deps.snap b/crates/oxc_linter/src/snapshots/react_exhaustive_deps.snap index 0a3aedd5eaf83..35748c37ce865 100644 --- a/crates/oxc_linter/src/snapshots/react_exhaustive_deps.snap +++ b/crates/oxc_linter/src/snapshots/react_exhaustive_deps.snap @@ -1907,6 +1907,15 @@ source: crates/oxc_linter/src/tester.rs ╰──── help: Consider putting the asynchronous code inside a function and calling it from the effect. + ⚠ eslint-plugin-react-hooks(exhaustive-deps): React Hook useCallback has a missing dependency: 'prop' + ╭─[exhaustive_deps.tsx:4:14] + 3 │ prop.hello(foo); + 4 │ }, [foo]); + · ───── + 5 │ const bar = useCallback(() => { + ╰──── + help: Either include it or remove the dependency array. + ⚠ eslint-plugin-react-hooks(exhaustive-deps): React Hook useEffect has a missing dependency: 'local' ╭─[exhaustive_deps.tsx:6:31] 5 │ } @@ -2248,3 +2257,12 @@ source: crates/oxc_linter/src/tester.rs 11 │ ╰──── help: Either include it or remove the dependency array. + + ⚠ eslint-plugin-react-hooks(exhaustive-deps): React Hook useEffect has a missing dependency: 'log' + ╭─[exhaustive_deps.tsx:9:12] + 8 │ log(); + 9 │ }, []); + · ── + 10 │ } + ╰──── + help: Either include it or remove the dependency array.