React expert
Framework & language expert skills for Claude Code — idiomatic best practices for TypeScript, React, Vue, Svelte, Solid, Angular, Astro
npx -y skills add Akayashuu/agent-skills --skill react-expertAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when writing, reviewing, or refactoring React components — taming re-renders, deciding what belongs in state vs derived during render, fixing useEffect overuse/race conditions, key/list bugs, useMemo/useCallback noise, context, refs, or server vs client component ("use client") boundaries.
SKILL.md
7.3 KB, as published. Nobody here has run it
React Expert
Overview
Idiomatic React is derive, don't store; render, don't sequence. Most state is redundant and most effects are a smell — the UI is a pure function of props, state, and context, and useEffect exists only to sync with systems outside React. These are the judgment calls a linter can't make for you.
Quick Reference
| Goal | Do | Avoid |
|---|---|---|
| Value computable from props/state | compute in render | useState + useEffect to "keep it updated" |
| React to a prop/state change | derive in render, or key to reset | useEffect watching that value |
| Talk to a non-React system | useEffect (subscriptions, DOM, timers) | effects for data transforms |
| List items | stable id key | key={index} |
| Fetch data | framework / TanStack Query / RSC | hand-rolled useEffect + fetch |
| Avoid prop-drilling | composition (children) then context | one giant global context |
| Expensive recompute / referential dep | useMemo/useCallback with a real reason | memoizing everything by default |
| Form input | controlled, or uncontrolled + ref | mixing both on one field |
| Read latest props/state inside an Effect | useEffectEvent (stable in 19.2) | widening effect deps to silence the linter |
Core Patterns
Derive during render — don't mirror props/state into an effect. The effect runs after paint, so the UI flashes a stale value, and the extra render is pure waste:
// ❌ redundant state + effect: extra render, stale frame, easy to desync
function Cart({ items }: { items: Item[] }) {
const [total, setTotal] = useState(0)
useEffect(() => { setTotal(items.reduce((s, i) => s + i.price, 0)) }, [items])
return <p>{total}</p>
}
// ✅ it's just a value; compute it. Wrap in useMemo only if profiling says so.
const total = items.reduce((s, i) => s + i.price, 0)
Runnable:
examples/derive-during-render.tsx
Reset state on identity change with key, not an effect. Changing the key remounts the subtree, discarding old state — no manual resync:
// ❌ effect to "reset form when user changes" — stale between switch and effect
useEffect(() => { setDraft('') }, [userId])
// ✅ a different user is a different form; let React remount it
<ProfileForm key={userId} />
Runnable:
examples/reset-with-key.tsx
Effects are for external systems — and must clean up to dodge races. Anything async needs an ignore flag or AbortController, which is exactly why a data library is better:
// ❌ no cleanup: a slow earlier request resolves last and overwrites the new one
useEffect(() => { fetch(`/api/u/${id}`).then(r => r.json()).then(setUser) }, [id])
// ✅ ignore stale resolutions (or just use TanStack Query / an RSC loader)
Runnable:
examples/abortable-effect-fetch.tsx
Composition kills prop-drilling before context does. Pass rendered UI as children so intermediate components never see props they only forward:
// ❌ Layout threads `user` through just to hand it down
<Layout user={user} /> // Layout forwards user → Sidebar → Avatar
// ✅ render the leaf where the data lives; Layout stays agnostic
<Layout sidebar={<Avatar user={user} />} />
Separate non-reactive logic with useEffectEvent — don't lie to the deps array. Stable since React 19.2: an Effect Event always sees the latest props/state but isn't reactive, so it stays out of the deps array — for fresh values an Effect shouldn't re-synchronize on:
const onConnected = useEffectEvent(() => log('connected', theme)) // reads latest theme
useEffect(() => {
const conn = createConnection(roomId)
conn.on('connected', () => onConnected())
conn.connect()
return () => conn.disconnect()
}, [roomId]) // re-runs on roomId only; theme change does NOT reconnect
Runnable:
examples/use-effect-event.tsx
Memoize for referential stability, not "speed." A useCallback/useMemo matters when its result is a dependency of a memoized child or another hook — otherwise it's noise that adds its own cost:
const onSelect = useCallback((id: string) => dispatch(select(id)), [dispatch]) // stable prop for memo'd <Row>
React Compiler (1.0, stable Oct 2025) changes this calculus. It auto-memoizes at build time, so with the compiler on you can delete most hand-written useMemo/useCallback/React.memo. Keep them only where it bails out (components that break the Rules of React). The compiler optimizes; it won't fix stored derived state or over-broad effect deps.
Common Mistakes
useEffectto compute derived data — transform in render; effects are for syncing with the outside world.key={index}— on reorder/insert, React reuses the wrong DOM/state. Use a stable id.- Stale closures — a callback captures the render's values; reading "latest" inside an Effect is what
useEffectEventis for — not a lie to the deps array. - Over-broad or missing deps — don't silence
react-hooks/exhaustive-deps; fix the design (move logic out, use a ref, or a functionalsetState(prev => …)). setStateduring render without a guard → infinite loop. Derive instead.- Premature
memo/useMemoeverywhere — measure first; memoization isn't free. - One giant context — every consumer re-renders on any field change. Split by concern (or by state vs dispatch).
reffor things render should own — refs are an imperative escape hatch (focus, measure, integrate non-React libs), not a place to stash render data.- Reading data in a Server Component then marking the whole tree
'use client'— keep'use client'at the interactive leaf; fetch on the server.
When NOT to over-engineer
Local, cheap, self-contained UI state (a toggle, an input) needs no reducer, no context, no memo. Reach for useReducer, context splitting, or a data library when state is shared, complex, or async — not preemptively. In React 19 (stable), prefer the platform: use to unwrap promises/context, <form> Actions + useActionState for submission and pending UI, useOptimistic for optimistic updates, ref-as-prop (no more forwardRef), and Server Components to fetch on the server so the client ships less and avoids effect-based fetching entirely. Don't hand-roll what the framework now does.
See examples/ for self-contained, compiling versions of each pattern above (and patterns.tsx for the same three combined in one module).