Step 7 of 29
useState, replacement updates, derived state with useMemo, lifting state up, rules of hooks
useState, immutable updates, lifting state — state model ที่ bug ส่วนใหญ่ย้อนกลับมาที่นี่
useState gives a component memory — a value that survives re-renders and triggers them when changed. Hooks are the mechanism: functions that let function components use state, effects, and context.
React re-renders your component function constantly; plain local variables reset every time. useState is the sanctioned persistence, and its rules — call hooks unconditionally at the top level, update through the setter — are what keep re-renders predictable. Most React bugs trace back to fighting this model instead of using it.
useStateimport { useState } from 'react'
function TaskFilter({ onFilter }: { onFilter: (p: Priority) => void }) {
const [priority, setPriority] = useState<Priority>('all')
return (
<select
value={priority}
onChange={e => {
setPriority(e.target.value as Priority)
onFilter(e.target.value as Priority)
}}
>
<option value="all">All</option>
<option value="low">Low</option>
<option value="high">High</option>
</select>
)
}
const [task, setTask] = useState<Task>({ title: '', done: false })
// wrong — mutation is invisible to React
task.done = true
// right — new object, new render
setTask({ ...task, done: true })
State from arrays and objects requires new references:
setTasks(prev => [...prev, newTask])
setTasks(prev => prev.filter(t => t.id !== id))
The updater form (prev =>) is mandatory when the next value depends on the current one.
Event handlers batch their state updates into one re-render. Multiple set calls in a handler apply together — do not read state mid-handler expecting intermediate renders.
Two siblings need the same value → move the state to their closest common parent and pass it down:
function TaskPage() {
const [tasks, setTasks] = useState<Task[]>([])
const [filter, setFilter] = useState<Priority>('all')
const visible = useMemo(
() => tasks.filter(t => filter === 'all' || t.priority === filter),
[tasks, filter]
)
return (
<>
<TaskFilter priority={filter} onFilter={setFilter} />
<TaskList tasks={visible} />
</>
)
}
Derived values (visible) are computed during render — not stored in state. useMemo caches expensive derivations keyed on dependencies.
React relies on call order to pair hook state with the component; conditional calls break that pairing.
Loading diagram...
useState(props.value) goes stale when the prop changes. Derive or key the component instead.doneCount belongs in useMemo (or plain render), not a second useState synced by hand.if statements. Order must be identical across every render — hoist conditionals inside the hook, not around it.