noctcore-react/no-state-in-component-body
State, effect, and query hooks belong in the colocated hook file, not the component body.
Recommended preset: error · Autofix: no · Suggestions: no · Type information: not needed
A component file (<Name>.tsx) should read like a thin presentation shell: props in, JSX out. When
useState, useEffect, useQuery, or a store hook lives directly in the render body, the component
owns behavior it should merely consume. Moving that logic into the colocated hook file
(<Name>.hooks.ts by convention) keeps the shell declarative, makes the behavior independently
testable, and keeps re-render surfaces small.
What it flags
Section titled “What it flags”Inside a component file (.tsx that is not a hook file), a call to any of:
- React stateful hooks —
useState,useReducer,useEffect,useLayoutEffect,useInsertionEffect,useMemo,useCallback,useRef,useImperativeHandle; - react-query data hooks —
useQuery,useMutation,useInfiniteQuery,useSuspenseQuery,useQueries; - store hooks matching
storeHookPattern(defaultuse*Store); - anything in
additionalHooks.
// state in the component bodyexport default function Widget() { const [open, setOpen] = useState(false); return <div />;}Moving the state to the colocated hook file is the fix:
// the same state, colocated in Widget.hooks.tsexport function useWidget() { const [open, setOpen] = useState(false); return { open, setOpen };}What it does not flag
Section titled “What it does not flag”useId, useTransition, and useDeferredValue are render-safe and allowlisted. Files whose basename
ends with a hookFileSuffixes entry (default .hooks.ts) are skipped — that is where the hooks
belong.
Stateful hooks in a plain .ts file (use-thing.ts) are not gated either: only .tsx component
files are checked.
Options
Section titled “Options”| Option | Type | Default | Meaning |
|---|---|---|---|
allowedHooks |
string[] |
['useId', 'useTransition', 'useDeferredValue'] |
Hook names that are always render-safe. |
additionalHooks |
string[] |
[] |
Extra hook names to flag. |
storeHookPattern |
string |
^use[A-Z][A-Za-z0-9]*Store$ |
Regex marking store hooks as stateful. |
hookFileSuffixes |
string[] |
['.hooks.ts'] |
Basename suffixes identifying the colocated hook file (skipped). |
'noctcore-react/no-state-in-component-body': ['error', { additionalHooks: ['useBridge'] }]When not to use it
Section titled “When not to use it”If you do not separate component shells from colocated hook files, this rule will fight your layout.
It assumes state lives in a sibling hook file rather than the .tsx.