Imagine that the navigation systems of your spaceship operate based on state data - speed, course, fuel level. Every update to these parameters must be precise, because even a small error can throw the ship out of orbit. In React, state updates follow similar rules - you must know the patterns for safe updates so your application behaves predictably.
When you call
setState, you have two options. The first is to pass a new value directly:1setCount(count + 1);The second is a functional update -- passing a function that receives the previous state value:
1setCount(prev => prev + 1);Why does this matter? React batches state updates (batching) and does not execute them immediately. If you call
setCount(count + 1) multiple times in a single handler, each call sees the same value of count:1function FuelGauge() {
2 const [fuel, setFuel] = useState(50);
3
4 const handleTripleBoost = () => {
5 // All three see the same value of fuel!
6 // Result: fuel will increase by only 10, not 30
7 setFuel(fuel + 10);
8 setFuel(fuel + 10);
9 setFuel(fuel + 10);
10 };
11
12 const handleTripleBoostCorrect = () => {
13 // Each call receives the current value
14 // Result: fuel will increase by 30
15 setFuel(prev => prev + 10);
16 setFuel(prev => prev + 10);
17 setFuel(prev => prev + 10);
18 };
19
20 return (
21 <div>
22 <p>Fuel: {fuel}%</p>
23 <button onClick={handleTripleBoost}>Boost (incorrect)</button>
24 <button onClick={handleTripleBoostCorrect}>Boost (correct)</button>
25 </div>
26 );
27}Rule: Always use functional updates (
prev => ...) when the new value depends on the previous one.You have two strategies for storing state in a component. The first is separate state variables:
1function ShipDashboard() {
2 const [speed, setSpeed] = useState(0);
3 const [altitude, setAltitude] = useState(0);
4 const [heading, setHeading] = useState(0);
5
6 // Each variable updated independently
7 const accelerate = () => setSpeed(prev => prev + 10);
8 const climb = () => setAltitude(prev => prev + 100);
9
10 return (
11 <div>
12 <p>Speed: {speed} km/s | Altitude: {altitude} km | Heading: {heading}°</p>
13 <button onClick={accelerate}>Accelerate</button>
14 <button onClick={climb}>Climb</button>
15 </div>
16 );
17}The second is state in an object -- useful when the data is related:
1function ShipDashboard() {
2 const [navigation, setNavigation] = useState({
3 speed: 0,
4 altitude: 0,
5 heading: 0
6 });
7
8 // IMPORTANT: always copy the entire object with the spread operator!
9 const accelerate = () => {
10 setNavigation(prev => ({ ...prev, speed: prev.speed + 10 }));
11 };
12
13 return (
14 <div>
15 <p>Speed: {navigation.speed} km/s</p>
16 <button onClick={accelerate}>Accelerate</button>
17 </div>
18 );
19}When to use which?
useState -- when values change independently of each otheruseState -- when values logically belong together (e.g., a form, x/y position)In React, state is immutable. Never modify state directly -- always create a new copy. It is like a safety rule on a spaceship: you do not edit the ship's log, you just add a new entry.
1function MissionLog() {
2 const [logs, setLogs] = useState(["Mission started"]);
3
4 const addLog = (entry) => {
5 // MISTAKE: push mutates the existing array!
6 // React will not detect the change and will not re-render the component
7 logs.push(entry);
8 setLogs(logs);
9 };
10
11 const addLogCorrect = (entry) => {
12 // Spread creates a new array
13 setLogs(prev => [...prev, entry]);
14 };
15
16 return (
17 <div>
18 <button onClick={() => addLogCorrect("New entry")}>
19 Add Entry
20 </button>
21 {logs.map((log, i) => <p key={i}>{log}</p>)}
22 </div>
23 );
24}1function CrewMember() {
2 const [member, setMember] = useState({
3 name: "Captain Nova",
4 rank: "Captain",
5 missions: 12
6 });
7
8 const promote = () => {
9 // MISTAKE: direct mutation of the object
10 member.rank = "Admiral";
11 setMember(member);
12 };
13
14 const promoteCorrect = () => {
15 // Creating a new object with the spread operator
16 setMember(prev => ({ ...prev, rank: "Admiral" }));
17 };
18
19 return (
20 <div>
21 <p>{member.name} — {member.rank}</p>
22 <button onClick={promoteCorrect}>Promote</button>
23 </div>
24 );
25}A closure is a situation where a function "remembers" variables from the moment it was created. In React, this can lead to reading outdated state:
1function CountdownTimer() {
2 const [seconds, setSeconds] = useState(10);
3
4 const startCountdown = () => {
5 // seconds is "frozen" from the moment of the click!
6 // Every interval sees the same value
7 setInterval(() => {
8 setSeconds(seconds - 1); // always 10 - 1 = 9
9 }, 1000);
10 };
11
12 const startCountdownCorrect = () => {
13 // Functional update always uses the current value
14 setInterval(() => {
15 setSeconds(prev => prev - 1);
16 }, 1000);
17 };
18
19 return (
20 <div>
21 <p>Countdown: {seconds}s</p>
22 <button onClick={startCountdownCorrect}>Start</button>
23 </div>
24 );
25}React 18+ automatically batches multiple state updates in a single event handler, performing only one re-render. It is like the onboard computer that collects all sensor reports and updates the display once, instead of refreshing it after each sensor:
1function NavigationUpdate() {
2 const [position, setPosition] = useState({ x: 0, y: 0 });
3 const [velocity, setVelocity] = useState(0);
4 const [status, setStatus] = useState("idle");
5
6 const handleJump = () => {
7 // React batches these 3 updates into one render
8 setPosition({ x: 100, y: 200 });
9 setVelocity(9999);
10 setStatus("hyperjump");
11 // The component renders ONCE, not three times!
12 };
13
14 return (
15 <div>
16 <p>Position: ({position.x}, {position.y})</p>
17 <p>Velocity: {velocity} | Status: {status}</p>
18 <button onClick={handleJump}>Hyperspace Jump</button>
19 </div>
20 );
21}Here are some patterns that will come in handy on board the React ship:
1// Removing an element from an array
2setItems(prev => prev.filter(item => item.id !== idToRemove));
3
4// Updating one element in an array
5setItems(prev => prev.map(item =>
6 item.id === targetId ? { ...item, status: "done" } : item
7));
8
9// Toggle (switching a boolean)
10setIsActive(prev => !prev);
11
12// Adding an element to the beginning of an array
13setMessages(prev => [newMessage, ...prev]);
14
15// Incrementing with a limit
16setFuel(prev => Math.min(prev + 10, 100));Updating state in React -- key principles:
prev => prev + 1) -- use when the new value depends on the previous oneRemember: State in React is like the ship's log -- you never edit it, you just create a new entry based on the previous one!