Update from latest, immutably
Use functional setState for derived updates, and never mutate state.
React docsreact.dev
If your project has React Compiler enabled, the compiler can automatically optimize some cases, but functional updates are still recommended for correctness and to prevent stale closure bugs.
From vercel-react-best-practices: rerender-functional-setstate and js-tosorted-immutable.
A callback stuck on mount
Add three todos, then remove one on each side.
Reads from closureBad
addItemsremoveItem
setItems(items.filter(…))deps []Functional updateGood
addItemsremoveItem
setItems(curr => curr.filter(…))deps []const addItems = useCallback((newItems) =>
setItems((curr) => [...curr, ...newItems]), [])
const removeItem = useCallback((id) =>
setItems((curr) => curr.filter((item) => item.id !== id)), [])
Three updates in one click
Click fast, or turn on the delay.
setCount(count + 1)Badexpected 0in step
setCount(c => c + 1)Goodexpected 0in step
function onClick() {
setCount((c) => c + 1)
setCount((c) => c + 1)
setCount((c) => c + 1)
}
setTimeout(() => setCount((c) => c + 1), 800)
A child sorts parent's array
Re-render the parent, or change the sort key.
users.sort(cmp)BadParent join order
Child by age
userscontents- Parent list
sorted === users
useMemo(() => users.sort(cmp), [users, key])users.toSorted(cmp)GoodParent join order
Child by age
userscontents- Parent list
sorted === users
useMemo(() => users.toSorted(cmp), [users, key])const sorted = useMemo(
() => users.toSorted((a, b) => a.name.localeCompare(b.name)),
[users]
)
Why it matters, pros and cons
Why it matters
An updater gets the latest state. .sort() mutates the owner's array in place.
Pros
- Stable callbacks with
[]deps - Correct under batching, timeouts and async
- New references, so memo sees the change
Cons
- Values ignoring the old one: set directly
- Updaters must be pure; StrictMode runs them twice
toSortedcopies: O(n) memory per call