diff --git a/crates/oxc_linter/src/rules/react/exhaustive_deps.rs b/crates/oxc_linter/src/rules/react/exhaustive_deps.rs index 5b3f2c62dcb63..1b16dc5dd7fb4 100644 --- a/crates/oxc_linter/src/rules/react/exhaustive_deps.rs +++ b/crates/oxc_linter/src/rules/react/exhaustive_deps.rs @@ -855,14 +855,21 @@ fn is_stable_value<'a, 'b>( { // if the variables is a function, check whether the function is stable - let function_body: Option<&oxc_allocator::Box<'_, FunctionBody<'_>>> = - match init.get_inner_expression() { - Expression::ArrowFunctionExpression(arrow_func) => Some(&arrow_func.body), - Expression::FunctionExpression(func) => func.body.as_ref(), - _ => None, - }; + let function_body = match init.get_inner_expression() { + Expression::ArrowFunctionExpression(arrow_func) => Some(&arrow_func.body), + Expression::FunctionExpression(func) => func.body.as_ref(), + _ => None, + }; if let Some(function_body) = function_body { - return is_function_stable(function_body, ctx, component_scope_id); + return is_function_stable( + function_body, + declaration + .id + .get_binding_identifier() + .map(oxc_ast::ast::BindingIdentifier::symbol_id), + ctx, + component_scope_id, + ); } } @@ -935,7 +942,7 @@ fn is_stable_value<'a, 'b>( let Some(function_body) = function_body else { return false }; - is_function_stable(function_body, ctx, component_scope_id) + is_function_stable(function_body, None, ctx, component_scope_id) } _ => false, } @@ -943,6 +950,7 @@ fn is_stable_value<'a, 'b>( fn is_function_stable<'a, 'b>( function_body: &'b FunctionBody<'a>, + function_symbol_id: Option, ctx: &'b LintContext<'a>, component_scope_id: ScopeId, ) -> bool { @@ -952,8 +960,10 @@ fn is_function_stable<'a, 'b>( collector.found_dependencies }; - deps.iter() - .all(|dep| !is_identifier_a_dependency(dep.name, dep.reference_id, ctx, component_scope_id)) + deps.iter().all(|dep| { + dep.symbol_id.zip(function_symbol_id).is_none_or(|(l, r)| l != r) + && !is_identifier_a_dependency(dep.name, dep.reference_id, ctx, component_scope_id) + }) } // https://github.com/facebook/react/blob/fee786a057774ab687aff765345dd86fce534ab2/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js#L1742 @@ -3539,6 +3549,22 @@ fn test() { console.log(foo); }, [foo]); }", + // https://github.com/oxc-project/oxc/issues/10319 + r"import { useEffect } from 'react' + + export const Test = () => { + const handleFrame = () => { + setTimeout(handleFrame) + } + + useEffect(() => { + setTimeout(handleFrame) + }, []) + + return ( + <> + ) + }", ]; 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 a98f7f38d0677..0a3aedd5eaf83 100644 --- a/crates/oxc_linter/src/snapshots/react_exhaustive_deps.snap +++ b/crates/oxc_linter/src/snapshots/react_exhaustive_deps.snap @@ -2239,3 +2239,12 @@ source: crates/oxc_linter/src/tester.rs 9 │ } ╰──── help: Try memoizing this variable with `useRef` or `useCallback`. + + ⚠ eslint-plugin-react-hooks(exhaustive-deps): React Hook useEffect has a missing dependency: 'handleFrame' + ╭─[exhaustive_deps.tsx:10:14] + 9 │ setTimeout(handleFrame) + 10 │ }, []) + · ── + 11 │ + ╰──── + help: Either include it or remove the dependency array.