react-hooks-discipline
Skill0 starsChanged 2 months agoMITView on GitHub
React hooks that survive code review — exhaustive dependency arrays, render purity, custom hook boundaries, and the refs/rules that prevent stale-closure bugs. Use when writing or reviewing React function components or custom hooks.
In this repository11 other files it gives its agents.
- api-contract-designSkill
- browser-debuggingSkill
- component-reviewSkill
- css-architectureSkill
- frontend-testing-strategySkill
- nextjs-performanceSkill
- performance-budgetSkill
- state-managementSkill
- typescript-strictnessSkill
- vue3-compositionSkill
- web-a11ySkill
The file
We split this skill into the rules it gives an agent
5 rules. About 0 are common, 4 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 12
Never mutate refs during render (read-only in render.
- Very specific: line 13
Never call setState unconditionally in render or in the body of another component.
- Distinctive: line 28
If you *must* omit a dependency, write why in a comment and prefer restructuring:
- Distinctive: line 50
Every custom hook returns a stable shape (tuple or object — pick per codebase and stick to it).
- Distinctive: line 69
Event → effect split: if logic runs "when the user clicks", it lives in the handler, not in an effect watching a state flag.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
React hooks discipline
Render purity is the whole game
A component render must be a pure function of props + state + context. Anything else belongs in effects or event handlers.
- Never mutate refs during render (read-only in render; write in effects/handlers).
- Never call
setStateunconditionally in render or in the body of another component. - Derived data →
useMemoor plain computation, not a seconduseStatekept in sync:
// ❌ duplicated state to keep in sync
const [fullName, setFullName] = useState('')
useEffect(() => setFullName(`${first} ${last}`), [first, last])
// ✅ derived
const fullName = `${first} ${last}`Dependency arrays: be exhaustive, then be honest
- Enable
eslint-plugin-react-hooksexhaustive-depsand treat warnings as errors. - If you must omit a dependency, write why in a comment and prefer restructuring:
// ❌ lie: latest always wins, silently breaks subscription
useEffect(() => {
const id = setInterval(() => poll(url), 5_000)
return () => clearInterval(id)
}, []) // url "won't change" — until it does
// ✅ restructure: keep it fresh via ref, or include it
const urlRef = useRef(url)
useEffect(() => { urlRef.current = url }, [url])
useEffect(() => {
const id = setInterval(() => poll(urlRef.current), 5_000)
return () => clearInterval(id)
}, [])- Objects/arrays/functions as deps re-fire every render — stabilize at the source (
useMemo/useCallbackat the producer, or hoist constants out).
Custom hooks: same rules as components
- Every custom hook returns a stable shape (tuple or object — pick per codebase and stick to it).
- Effects in custom hooks still clean up. If it subscribes, it unsubscribes — no exceptions.
- One behavior per hook.
useUserWithPostsAndCommentsis three hooks wearing a trench coat.
The patterns that prevent 90% of bugs
- Latest-value ref for subscriptions/intervals that shouldn't re-arm (above).
- Abort on unmount for fetches:
useEffect(() => {
const ac = new AbortController()
fetch(url, { signal: ac.signal })
.then(setData)
.catch((e) => e.name !== 'AbortError' && setError(e))
return () => ac.abort()
}, [url])- Event → effect split: if logic runs "when the user clicks", it lives in the handler, not in an effect watching a state flag.
Anti-patterns
- Conditional hooks or hooks in loops/conditions (rule of hooks — non-negotiable).
useEffectchains mirroring imperative flows (a → b → c). Model state, not steps.- Context value recreated every render without memoization — memoize the
valueobject or split contexts. - Storing functions in state; store data, derive behavior.
Definition of done (agent checklist)
exhaustive-depsclean, deviations documented with reason- Every effect with a subscription/timer/fetch has cleanup
- No state that is derivable from props/state
- Keys on lists are stable ids
- No ref writes during render
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

