State is data that can change over time - a form input, a toggle button, items in a shopping cart. When state changes, React automatically updates the UI to match.The useState hook is how you add state to function components. You’ll use it constantly.
function Example() { const [count, setCount] = useState(0); const handleClick = () => { console.log('Before:', count); // 0 setCount(5); console.log('After:', count); // Still 0! }; return <button onClick={handleClick}>Set to 5</button>;}
State updates are scheduled (and often batched). When you call setCount(5), React schedules a re-render but doesn’t update count immediately inside the current event handler. The new value will be available on the next render.
Don’t try to use the updated state value immediately after calling the setter in the same event handler. It won’t be updated yet. React batches state updates for performance.
function Counter() { const [count, setCount] = useState(0); // ❌ Wrong when updating multiple times const handleIncrement = () => { setCount(count + 1); setCount(count + 1); // Both use the same 'count' value! }; // ✅ Correct: Use updater function const handleIncrementTwice = () => { setCount(prev => prev + 1); // Gets latest value setCount(prev => prev + 1); // Gets latest value }; return ( <div> <p>Count: {count}</p> <button onClick={handleIncrement}>Add 1 (broken)</button> <button onClick={handleIncrementTwice}>Add 2 (works)</button> </div> );}
When updating based on the previous state, use the updater function form: setCount(prev => prev + 1). This ensures you always get the latest value, even if multiple updates happen.
If your new state depends on the old state, use the updater function: setState(prev => ...). If it doesn’t, you can pass the value directly: setState(newValue).
Never mutate state directly. Use the spread operator ... to create a new object with the updated property. React only detects changes when you pass a new object to the setter.
Direct mutation (user.age = 26) won’t trigger a re-render. React compares the old and new values - if they’re the same object reference, it thinks nothing changed.
Never use .push(), .pop(), .splice() or other mutating methods on state arrays. Always create a new array with spread ... or array methods like .map() and .filter().
// ❌ Wrong: Direct mutationconst [user, setUser] = useState({ name: "Sarah", age: 25 });function updateAge() { user.age = 26; // React won't detect this! setUser(user); // Still the same object reference}// ✅ Correct: Create new objectfunction updateAge() { setUser({ ...user, age: 26 });}
React only detects changes when you call the setter with a NEW object or array. Mutating the existing state and passing it back won’t trigger a re-render because it’s still the same reference.
Using stale state in updates
Copy
// ❌ Wrong: Using current state value directlyconst [count, setCount] = useState(0);function incrementTwice() { setCount(count + 1); // Uses 0 setCount(count + 1); // Also uses 0, because count hasn't updated yet // Result: count is 1, not 2}// ✅ Correct: Use updater functionfunction incrementTwice() { setCount(prev => prev + 1); // Uses latest value setCount(prev => prev + 1); // Uses latest value // Result: count is 2}
When multiple state updates happen in the same function, use the updater function form to ensure you’re always working with the latest value.
Forgetting that state updates are asynchronous
Copy
// ❌ Wrong: Expecting immediate updatefunction handleClick() { setCount(5); console.log(count); // Still the old value!}// ✅ Correct: Use the new value in next renderfunction handleClick() { setCount(5); // Don't try to use the new value here}// The new value will be available in the next render:console.log(count); // This will show 5 on the next render
State updates are asynchronous. The new value won’t be available until the component re-renders. Don’t try to use the updated state immediately after calling the setter.
When rendering lists, each item needs a unique key prop. This helps React efficiently update the DOM when items are added, removed, or reordered. Never use array index as a key if the list can change.