Updating State Correctly
In this page:
Never Mutate State Directly
React decides whether to re-render partly by checking if the state value changed. If you mutate the existing value in place instead of creating a new one, React may not detect the change and the UI won't update, even though the underlying data did change.
Note: Always create a new value (a new object, array, or primitive) when updating state, rather than modifying the existing one.
Warning: Something like state.push(newItem) followed by setState(state) often fails to re-render because the array reference didn't change.
Example: Never Mutate State Directly
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function Toggle() {
const [isOn, setIsOn] = React.useState(false);
return (
<button onClick={() => setIsOn(!isOn)}>
{isOn ? "ON" : "OFF"}
</button>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<Toggle />);
</script>
</body>
</html>
State Updates Are Not Instant
Calling a state setter schedules an update for the next render -- it does not change the variable's value immediately within the currently-running function. If you log the state variable right after calling its setter, you'll still see the old value.
Note: If you need to act on the new value immediately, compute it into a separate local variable first, rather than relying on the state variable.
Warning: Code like setCount(count + 1); console.log(count); will log the OLD count, not the new one -- this surprises many beginners.
Example: State Updates Are Not Instant
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function Demo() {
const [count, setCount] = React.useState(0);
function handleClick() {
setCount(count + 1);
// count here is still the OLD value until the next render
}
return <button onClick={handleClick}>Count: {count}</button>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<Demo />);
</script>
</body>
</html>
State Persists Between Renders
Unlike a normal local variable that resets every time a function runs, state declared with useState is preserved by React across re-renders of the same component instance. This is exactly what lets a component remember things like a counter value over time.
Note: Think of state as living outside the function, in a place React manages, even though you declare it inside the function body.
Warning: A plain let variable declared inside a component resets to its initial value on every render -- only useState (or similar Hooks) actually persists.
Example: State Persists Between Renders
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function Counter() {
const [count, setCount] = React.useState(0);
// A plain variable here would reset to 0 every render; state does not.
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<Counter />);
</script>
</body>
</html>
- Mutating an object or array in state directly instead of creating a new copy.
- Relying on the state variable's value immediately after calling its setter in the same function.
- Forgetting that state updates from the previous render are what you see until the next render happens.
- State should always be updated through its setter function, never mutated directly.
- React compares old and new state to decide whether to re-render.
- State updates take effect on the next render, not instantly.
- Treating state as immutable prevents subtle bugs in React apps.
No browser-specific restrictions -- applies to useState since React 16.8.
Chapter Quiz — Complete all 10 topics to unlock
0/10 topics done
Complete these topics first: