The React rules
Why Straitjacket enforces one component per file and keeps effects out of component files — and how the AST analysis works.
Four rules are React-specific and only ever fire on .tsx/.jsx, so they're
inert in non-React repos. They're AST-based, built on OXC — the
same parser and semantic analyzer used by the Rust JavaScript tooling ecosystem.
This page covers one-component and effect-in-component; the two forwarding
rules get their own page, Prop-drilling and drill depth.
Why AST, not regex
React structure isn't reliably detectable with pattern matching — you need to
know what's a component, what's a hook call, which scope a binding lives in. OXC
parses the file and builds a semantic model (scopes, symbols, references), and
the rules ask questions of that model. That's what lets prop-drilling
distinguish a forwarded-but-unused prop from one the component actually reads.
one-component
Flags more than one React component declared in a single .tsx/.jsx file.
One component per file keeps a codebase navigable: the file name tells you what's in it, imports stay honest, and a component can't quietly grow three siblings that should have been their own files. LLMs love to inline a little helper component right next to the one you asked for — this catches that.
effect-in-component
Flags a useEffect defined in a component's body.
The rule isn't "never use effects" — it's that effects belong in a named custom
hook (useThing), not tangled inline into a component. The line is precise: an
effect inside a use* hook is fine, even in the same file as a component. So
you don't have to spray your codebase with one-hook-per-file — a component and its
hooks can share a file; only the useEffect-directly-in-the-component adjacency is
flagged. Anonymous components (memo(() => …), forwardRef) count as components.
The reasoning: effects are where the tricky, stateful, easy-to-get-wrong logic
lives. Pulling each one into a named hook gives it a name, a home, and a testable
boundary — and stops component bodies from accreting side effects that are hard to
spot in review. LLMs reach for useEffect reflexively, often where derived state or
an event handler would do; forcing the effect into a named hook makes each one a
deliberate, visible choice. Whether the hook is reusable is beside the point — the
value is that it's named and out of the component.
It's AST-based (OXC): straitjacket finds every useEffect call and checks whether
its nearest enclosing function is a component (flag) or a hook (fine).
Turning them off
Both are on by default. Disable individually with
--skip one-component or --skip effect-in-component. They never fire outside
.tsx/.jsx, so there's nothing to disable in a non-React repo.
Why duplication is compiled in
Copy-paste is a loud LLM tell, so Straitjacket detects clones in-process with no external tool.
Prop-drilling and drill depth
How Straitjacket flags a pure conduit prop with semantic analysis, why it excludes the cases it does, and how --prop-chains measures depth across files.