Frontend Master

JavaScript Curriculum

State — useState
+50 XP

State — useState

easy
~30 min·50 XP

The coffee shop cart needs to update when customers add items. The order form needs to track what the user is typing. The filter needs to remember which category is active. All of this is state.

State — useState

State is memory that persists across re-renders. Every time you call the setter, React schedules a re-render and your component function runs again with the new value. This is the core mechanism that makes React UIs dynamic.

useState — Interactive Explorer

Try all three common state types — number, string, boolean:

use-state.jsxrenders: 0
0
current state
const [count, setCount] = useState(0)

// Functional update (safe):
setCount(c => c + 1)  // → 1
setCount(c => c - 1)  // → -1
setCount(0)           // reset
ℹ️useState returns a pair
`const [value, setValue] = useState(initialValue)` — array destructuring gives you the current value and the setter function. The initial value is only used on the first render. On re-renders, React returns the current state, not the initial value.

What Triggers a Re-render?

Not all state updates cause a re-render — learn the rules:

render-counter.jsx
stateA
0
stateB
0
React scheduler log
// Initial render — component mounts
Batching: React 18 batches all state updates — even in async handlers — into one re-render.
Bail-out: If new state === old state (Object.is), React skips the re-render.
💡React batches state updates
In React 18+, multiple `setState` calls in the same event handler are batched into a single re-render. This is why calling `setA(1); setB(2)` triggers ONE render, not two. React flushes the batch at the end of the event.

State Update Patterns

The rules for updating primitives, objects, and arrays correctly:

state-patterns.jsx

Use the functional form when the new state depends on the old state. Avoids stale closures.

❌ Avoid
// ⚠ Can be stale in async contexts:
const [count, setCount] = useState(0)

// If called multiple times rapidly,
// count may be stale:
setCount(count + 1)
setCount(count + 1) // both use same stale count!
// result: count = 1, not 2
✓ Do this
// ✓ Functional update — always fresh:
setCount(prev => prev + 1)
setCount(prev => prev + 1)
// result: count = 2 ✓

// Toggle pattern:
setIsOpen(prev => !prev)

// Append to array:
setItems(prev => [...prev, newItem])
⚠️Never mutate state directly
React uses reference equality to detect changes. If you mutate an object or array and pass the same reference to the setter, React sees no change and skips the re-render. Always create a new value: spread objects, use filter/map for arrays.

Your Challenge

Build CartSummary: state is items = []. addItem(item)setItems(prev => [...prev, item]). removeItem(id)setItems(prev => prev.filter(x => x.id !== id)). Display total: items.reduce((sum, x) => sum + x.price, 0).toFixed(2). Add 3 buttons to add items and verify the total updates.

Challenge

Build a CartSummary component with useState. Track an items array — start empty. Add an addItem(item) function that appends to the array immutably. Add a removeItem(id) function that filters. Display the item count and total price.

useStatestatere-rendersetterimmutabilityfunctional-updatecontrolled-inputstate-initializationbatching
State — useState | Nexus Learn