Kaiber
Generate, edit, and beat-sync AI video with leading models in one workspace.
better-use-effect
Guides correct usage of React's useEffect hook — when to use it, when NOT to use it, and what modern alternatives exist. Use this skill whenever writing, reviewing, or refactoring React components that involve useEffect, side effects, data fetching, derived state, event handling, subscriptions, o...
Full skill instructions
useEffect exists for one purpose: synchronizing a component with an external system. If you are not connecting to a browser API, a WebSocket, a third-party widget, or some other system outside of React, you almost certainly do not need useEffect.
Before writing useEffect, walk through these questions in order — stop at the first match. See references/decision-flowchart.md for expanded examples of each case.
useMemo if expensive.use hook with Suspense.key prop for clean remount.useSyncExternalStore.// BAD — double render, state drift
const [filtered, setFiltered] = useState([])
useEffect(() => { setFiltered(items.filter(i => i.active)) }, [items])
// GOOD
const filtered = items.filter(i => i.active)
// BAD — race conditions, no caching
useEffect(() => { fetch(`/api/user/${id}`).then(r => r.json()).then(setUser) }, [id])
// GOOD
const { data: user } = useQuery(['user', id], () => fetchUser(id))
// BAD — flag relay
const [submitted, setSubmitted] = useState(false)
useEffect(() => { if (submitted) { postForm(); setSubmitted(false) } }, [submitted])
// GOOD
function handleSubmit() { postForm() }
// BAD
useEffect(() => { setDraft(''); setAttachments([]) }, [noteId])
// GOOD
<NoteEditor key={noteId} noteId={noteId} />
Multiple effects where one sets state that triggers another. Consolidate into the event handler or derive values inline. See references/anti-patterns.md for detailed examples.
Objects, arrays, and functions in dependency arrays cause effects to re-run every render because React uses reference comparison (Object.is). This applies equally to:
// Parent stabilizes
const data = useMemo(() => transform(raw), [raw])
return <Chart data={data} animate={true} color="blue" />
// Or child destructures to primitives
const { animate, color } = config
useEffect(() => { init({ animate, color }) }, [animate, color])
This is easy to miss because you own the state. If your effect only reads specific fields from a state object, depend on those fields — not the whole object.
// BAD — lastSaved is a new object after every save → effect re-runs
const [lastSaved, setLastSaved] = useState({ title: '', body: '' })
useEffect(() => {
const timer = setTimeout(() => save(title, body), 2000)
return () => clearTimeout(timer)
}, [title, body, lastSaved]) // lastSaved is an object — unstable
// GOOD — depend on the primitive fields you actually read
const lastSavedRef = useRef({ title: '', body: '' })
useEffect(() => {
const timer = setTimeout(() => {
save(title, body)
lastSavedRef.current = { title, body }
}, 2000)
return () => clearTimeout(timer)
}, [title, body]) // only primitives
If you need to compare current state to a previous snapshot, use a ref for the snapshot — refs don't trigger re-runs.
If your effect reads an entire array, do not depend on .length alone. The effect will miss content changes.
// BAD — stale if contents change but length stays the same
useEffect(() => { items.forEach(process) }, [items.length])
// GOOD — depend on what you read
useEffect(() => { items.forEach(process) }, [items])
// ANTIPATTERN — runs every render, breaks on non-serializable data
const stable = useMemo(() => data, [JSON.stringify(data)])
Callback props are functions — new reference every render unless the parent uses useCallback. If an effect calls a callback prop, stabilize it with a ref:
// BAD — effect re-fires every render
useEffect(() => {
const timer = setTimeout(() => onDismiss(id), 5000)
return () => clearTimeout(timer)
}, [id, onDismiss]) // onDismiss is unstable
// GOOD — ref-based stable wrapper
const onDismissRef = useRef(onDismiss)
onDismissRef.current = onDismiss
useEffect(() => {
const timer = setTimeout(() => onDismissRef.current(id), 5000)
return () => clearTimeout(timer)
}, [id]) // stable
If a callback is only used in event handlers (click, submit, etc.) and never in effects, it does not need stabilization — just call it directly. Only stabilize callbacks that appear in effect dependency arrays.
let ignore = false)setCount(c => c + 1) removes count from depsuseReducer for complex state — dispatch is always stableSee references/correct-usage.md for patterns. See references/modern-alternatives.md for React 19 hooks (use, useOptimistic, useActionState, useEffectEvent).
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience