Skip to content

Commit 4e3044e

Browse files
committed
fix(linter): rules-of-hooks fix false positive with default export (#7570)
closes #7555
1 parent cb1f8e6 commit 4e3044e

2 files changed

Lines changed: 25 additions & 32 deletions

File tree

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

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -172,13 +172,12 @@ impl Rule for RulesOfHooks {
172172
}) => {
173173
let ident = get_declaration_identifier(nodes, parent_func.id());
174174

175-
// Hooks cannot be called inside of export default functions or used in a function
176-
// declaration outside of a react component or hook.
175+
// Hooks cannot be used in a function declaration outside of a react component or hook.
177176
// For example these are invalid:
178177
// const notAComponent = () => {
179178
// return () => {
180179
// useState();
181-
// }
180+
// }
182181
// }
183182
// --------------
184183
// export default () => {
@@ -192,9 +191,7 @@ impl Rule for RulesOfHooks {
192191
// useState(0);
193192
// }
194193
// }
195-
if ident.is_some_and(|name| !is_react_component_or_hook_name(&name))
196-
|| is_export_default(nodes, parent_func.id())
197-
{
194+
if ident.is_some_and(|name| !is_react_component_or_hook_name(&name)) {
198195
return ctx.diagnostic(diagnostics::function_error(
199196
*span,
200197
hook_name,
@@ -400,14 +397,6 @@ fn get_declaration_identifier<'a>(
400397
})
401398
}
402399

403-
fn is_export_default<'a>(nodes: &'a AstNodes<'a>, node_id: NodeId) -> bool {
404-
nodes
405-
.ancestor_ids(node_id)
406-
.map(|id| nodes.get_node(id))
407-
.nth(1)
408-
.is_some_and(|node| matches!(node.kind(), AstKind::ExportDefaultDeclaration(_)))
409-
}
410-
411400
/// # Panics
412401
/// `node_id` should always point to a valid `Function`.
413402
fn is_memo_or_forward_ref_callback(nodes: &AstNodes, node_id: NodeId) -> bool {
@@ -916,6 +905,16 @@ fn test() {
916905
});
917906
});
918907
",
908+
"export default function App() {
909+
const [state, setState] = useState(0);
910+
911+
useEffect(() => {
912+
console.log('Effect called');
913+
}, []);
914+
915+
return <div>{state}</div>;
916+
}
917+
"
919918
];
920919

921920
let fail = vec![

crates/oxc_linter/src/snapshots/react_rules_of_hooks.snap

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -668,24 +668,18 @@ source: crates/oxc_linter/src/tester.rs
668668
5
669669
╰────
670670
671-
eslint-plugin-react-hooks(rules-of-hooks): React Hook "useState" is called in function "Anonymous" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
672-
╭─[rules_of_hooks.tsx:2:28]
673-
1 │
674-
2 │ ╭─▶ export default () => {
675-
3 │ │ if (isVal) {
676-
4 │ │ useState(0);
677-
5 │ │ }
678-
6 │ ╰─▶ }
679-
7
671+
eslint-plugin-react-hooks(rules-of-hooks): React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render.
672+
╭─[rules_of_hooks.tsx:4:21]
673+
3if (isVal) {
674+
4 │ useState(0);
675+
· ───────────
676+
5 │ }
680677
╰────
681678
682-
eslint-plugin-react-hooks(rules-of-hooks): React Hook "useState" is called in function "Anonymous" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".
683-
╭─[rules_of_hooks.tsx:2:28]
684-
1 │
685-
2 │ ╭─▶ export default function() {
686-
3 │ │ if (isVal) {
687-
4 │ │ useState(0);
688-
5 │ │ }
689-
6 │ ╰─▶ }
690-
7
679+
eslint-plugin-react-hooks(rules-of-hooks): React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render.
680+
╭─[rules_of_hooks.tsx:4:21]
681+
3if (isVal) {
682+
4 │ useState(0);
683+
· ───────────
684+
5 │ }
691685
╰────

0 commit comments

Comments
 (0)