Skip to content
Skill · Cursor.cursor/rules/*.mdcCoding

React Hooks Discipline

Hard rules for effect dependencies, deriving state instead of syncing it, and when code earns extraction into a custom hook.

Use when: Attach to TSX files in any React project to stop effect overuse, stale-closure bugs, and premature custom-hook abstraction.

@libraryNewcomer
0 upvotes0 forks0 comments

The skill file

Cursor · .cursor/rules/*.mdc
.cursor/rules/*.mdc3213 chars
--- description: Rules for useState/useEffect/useMemo usage and custom hook extraction globs: "**/*.tsx" alwaysApply: false --- # React hooks rules ## Derived state over effects - If a value can be computed from props or existing state during render, compute it during render. Never mirror it into state with a syncing effect. ```tsx // BAD: const [fullName, setFullName] = useState(""); useEffect(() => setFullName(`${first} ${last}`), [first, last]); // GOOD: const fullName = `${first} ${last}`; ``` - Wrap a derived value in `useMemo` only when the computation is measurably expensive (sorting/filtering large lists) or its identity feeds a dependency array. Memoizing string concatenation is noise — delete it. - To reset state when an identity prop changes, change the component's `key` from the parent. Do not write a `useEffect` that watches the prop and calls a setter. - "Adjust state during render" pattern (set state conditionally before returning, guarded by a comparison) is allowed only for the rare reset-on-prop-change case that `key` can't express; comment why. ## useEffect rules - An effect exists to synchronize with something outside React: subscriptions, DOM APIs, timers, analytics, imperative widgets. If an effect only calls `setState` from other state/props, it is a bug — derive instead. - Event logic belongs in event handlers. Never set a `submitted` flag in state and "react" to it in an effect; do the work in `handleSubmit`. - Dependency arrays are never lied to. Do not omit a dependency to "run once" — restructure instead: - stable callback identity → `useCallback` (or an effect-event-style ref) at the source; - object/array deps recreated each render → destructure to primitives or move the literal inside the effect; - genuinely mount-only browser setup → empty array is fine, but the effect body must not read changing props/state. - Every subscription/listener/timer effect returns a cleanup function. An `addEventListener` without a matching cleanup is a defect, not a style issue. - Fetching in effects requires an `AbortController` (or ignore-stale flag) in cleanup. If the project has React Query/SWR, fetching in `useEffect` is banned outright. ## Custom hook extraction thresholds Extract `useX()` into its own file only when ALL of these hold: 1. The logic spans 2+ hooks working together (e.g. state + effect + ref), not a single `useState`. 2. It is used by 2+ components, or it isolates one nameable external concern (`useMediaQuery`, `useDebouncedValue`, `useLocalStorage`). 3. It returns a small, named API — `{ value, setValue }` or `[state, actions]` — never the raw setter soup of its internals. Do not extract: one-component logic just to shorten the component, hooks that take 6+ parameters (split them), or hooks that render JSX (that's a component). ## Misc - Functional updates (`setCount(c => c + 1)`) whenever next state depends on previous state, and always inside async callbacks. - `useRef` for values that change without needing a re-render (latest callback, scroll position); never read/write refs during render. - Hooks are called unconditionally at the top of the component — early returns come after the last hook.

Install

drop into your repo

Save this to your project or home directory so Cursor can load it.

path./.cursor/rules/react-hooks-discipline.mdc
Discussion

What people are saying

Forks

0 forks of this skill
Loading forks…