Skip to content

Commit 2481964

Browse files
stipsancamc314
andauthored
feat(linter/exhaustive-deps): add support for useEffectEvent (#14041)
In order to support: https://react.dev/learn/separating-events-from-effects#declaring-an-effect-event We use https://www.npmjs.com/package/use-effect-event in a lot of places, and this is the last blocker for us to use oxlint --------- Signed-off-by: Cody Olsen <81981+stipsan@users.noreply.github.com> Co-authored-by: Cameron Clark <cameron.clark@hey.com>
1 parent 3a706a7 commit 2481964

File tree

2 files changed

+64
-2
lines changed

2 files changed

+64
-2
lines changed

crates/oxc_linter/src/rules/react/exhaustive_deps.rs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,17 @@ fn ref_accessed_directly_in_effect_cleanup_diagnostic(span: Span) -> OxcDiagnost
181181
.with_error_code_scope(SCOPE)
182182
}
183183

184+
fn functions_returned_from_use_effect_event_must_not_be_included_in_dependency_array(
185+
span: Span,
186+
) -> OxcDiagnostic {
187+
OxcDiagnostic::warn(
188+
"Functions returned from `useEffectEvent` must not be included in the dependency array.",
189+
)
190+
.with_label(span)
191+
.with_help("Remove the dependency from the dependency array.")
192+
.with_error_code_scope(SCOPE)
193+
}
194+
184195
#[derive(Debug, Default, Clone)]
185196
pub struct ExhaustiveDeps(Box<ExhaustiveDepsConfig>);
186197

@@ -591,6 +602,18 @@ impl Rule for ExhaustiveDeps {
591602
);
592603
}
593604

605+
for dep in &declared_dependencies {
606+
if let Some(symbol_id) = dep.symbol_id
607+
&& let AstKind::VariableDeclarator(var_decl) =
608+
ctx.semantic().symbol_declaration(symbol_id).kind()
609+
&& let Some(Expression::CallExpression(call_expr)) = &var_decl.init
610+
&& let Some(name) = func_call_without_react_namespace(call_expr)
611+
&& name == "useEffectEvent"
612+
{
613+
ctx.diagnostic(functions_returned_from_use_effect_event_must_not_be_included_in_dependency_array(dep.span));
614+
}
615+
}
616+
594617
// effects are allowed to have extra dependencies
595618
if !is_effect {
596619
// lastly, we need co compare for any unnecessary deps
@@ -1047,7 +1070,7 @@ fn is_stable_value<'a, 'b>(
10471070
return false;
10481071
};
10491072

1050-
if init_name == "useRef" {
1073+
if init_name == "useRef" || init_name == "useEffectEvent" {
10511074
return true;
10521075
}
10531076

@@ -2605,7 +2628,17 @@ fn test() {
26052628
r"function MyComponent(props) { useEffect(() => { console.log((props.foo).bar) }, [props.foo!.bar]) }",
26062629
r"function MyComponent(props) { const external = {}; const y = useMemo(() => { const z = foo<typeof external>(); return z; }, []) }",
26072630
r#"function Test() { const [state, setState] = useState(); useEffect(() => { console.log("state", state); }); }"#,
2608-
"function Test() { const [foo, setFoo] = useState(true); _setFoo = setFoo; useEffect(() => { setFoo(false) }, []); }",
2631+
"function MyComponent({ theme }) {
2632+
const onStuff = useEffectEvent(() => {
2633+
showNotification(theme);
2634+
});
2635+
useEffect(() => {
2636+
onStuff();
2637+
}, []);
2638+
React.useEffect(() => {
2639+
onStuff();
2640+
}, []);
2641+
}",
26092642
];
26102643

26112644
let fail = vec![
@@ -4063,6 +4096,17 @@ fn test() {
40634096
log();
40644097
}, []);
40654098
}"#,
4099+
r"function MyComponent({ theme }) {
4100+
const onStuff = useEffectEvent(() => {
4101+
showNotification(theme);
4102+
});
4103+
useEffect(() => {
4104+
onStuff();
4105+
}, [onStuff]);
4106+
React.useEffect(() => {
4107+
onStuff();
4108+
}, [onStuff]);
4109+
}",
40664110
];
40674111

40684112
let pass_additional_hooks = vec![(

crates/oxc_linter/src/snapshots/react_exhaustive_deps.snap

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3073,6 +3073,24 @@ source: crates/oxc_linter/src/tester.rs
30733073
╰────
30743074
help: Either include it or remove the dependency array.
30753075

3076+
eslint-plugin-react-hooks(exhaustive-deps): Functions returned from `useEffectEvent` must not be included in the dependency array.
3077+
╭─[exhaustive_deps.tsx:7:15]
3078+
6onStuff();
3079+
7 │ }, [onStuff]);
3080+
· ───────
3081+
8React.useEffect(() => {
3082+
╰────
3083+
help: Remove the dependency from the dependency array.
3084+
3085+
eslint-plugin-react-hooks(exhaustive-deps): Functions returned from `useEffectEvent` must not be included in the dependency array.
3086+
╭─[exhaustive_deps.tsx:10:15]
3087+
9onStuff();
3088+
10 │ }, [onStuff]);
3089+
· ───────
3090+
11 │ }
3091+
╰────
3092+
help: Remove the dependency from the dependency array.
3093+
30763094
eslint-plugin-react-hooks(exhaustive-deps): React Hook useSpecialEffect has a missing dependency: 'state'
30773095
╭─[exhaustive_deps.tsx:7:14]
30783096
5const someNumber: typeof state = 2;

0 commit comments

Comments
 (0)