State
Component Memory
LinkedIn Hook
You call
setCount(count + 1)three times in a row, and the count only goes up by 1.Wait, what?
Most React developers learn
useStatein their first hour, but most don't understand how it actually works until an interviewer catches them off guard.State isn't a simple variable. It's a snapshot. React batches your updates. And if you're not using functional updates, you're writing bugs you haven't found yet.
In this lesson, I break down state from the ground up — what it really is, why it's async, how batching works, functional updates vs direct updates, and when you should skip state entirely and use derived values instead.
If you've ever been confused by stale state in a
setTimeout— this one's for you.Read the full lesson → [link]
#React #JavaScript #InterviewPrep #Frontend #CodingInterview #ReactHooks #useState #100DaysOfCode
What You'll Learn
- What state is and why React needs it (instead of regular variables)
- How to use the
useStatehook correctly - Why state updates are asynchronous and how React batches them
- Functional updates — the safe way to update state based on previous value
- When to use state vs derived values (a common interview trap)
The Concept — What Is State?
Analogy: The Whiteboard in a Meeting Room
Imagine a meeting room with a whiteboard. Every time the team needs to track something — the current task, the vote count, the agenda item — they write it on the whiteboard.
A regular variable is like writing on a sticky note that gets thrown away every time someone walks in and out of the room. It doesn't persist.
State is the whiteboard. It persists between visits (re-renders). React looks at the whiteboard every time it re-renders the room, reads the current value, and draws the UI accordingly. When you update the whiteboard (setState), React schedules a new meeting (re-render) to reflect the change.
How useState Works
useState is a hook that gives you two things:
- The current value — what's on the whiteboard right now
- A setter function — the only way to update the whiteboard
Code Example 1: Basic Counter
import { useState } from "react";
function Counter() {
// Declare state: initial value is 0
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
// User clicks the button:
// Render 1: Count: 0
// Render 2: Count: 1
// Render 3: Count: 2
Key points:
useState(0)— the0is the initial value, used only on the first rendercount— the current state value for this rendersetCount— the function to schedule a state update (and a re-render)
Why Not Just Use a Regular Variable?
function BrokenCounter() {
let count = 0; // This resets to 0 on every render!
return (
<div>
<p>Count: {count}</p>
<button onClick={() => {
count = count + 1; // This changes the local variable...
console.log(count); // Output: 1 (it did change)
// ...but React doesn't know about it. No re-render happens.
}}>
Increment
</button>
</div>
);
}
// The button click updates the variable, but the UI never changes.
// Two problems:
// 1. Regular variables don't survive re-renders (they reset)
// 2. Changing a regular variable doesn't trigger a re-render
State Is Asynchronous — Batching
This is the number one interview trap with state. State updates do not happen immediately. React batches them.
Code Example 2: The Batching Trap
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1); // count is 0, so this schedules: set to 1
setCount(count + 1); // count is STILL 0 (same render), schedules: set to 1
setCount(count + 1); // count is STILL 0, schedules: set to 1
console.log(count); // Output: 0 (state hasn't updated yet)
}
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>+3?</button>
</div>
);
}
// User clicks the button:
// Expected: count becomes 3
// Actual: count becomes 1
//
// Why? All three setCount calls read the same `count` (0) from this render.
// React batches them: set 1, set 1, set 1 → final value is 1.
Why does React batch? Performance. If three state updates each triggered a separate re-render, the component would render three times for one click. Batching means React collects all the updates, figures out the final state, and re-renders once.
In React 18+, batching happens everywhere — inside event handlers, promises, setTimeout, and native event handlers. Before React 18, batching only worked inside React event handlers.
Functional Updates — The Fix
When your new state depends on the previous state, use a functional update. Instead of passing a value, pass a function that receives the previous state.
Code Example 3: Functional Updates
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(prev => prev + 1); // prev is 0 → returns 1
setCount(prev => prev + 1); // prev is 1 → returns 2
setCount(prev => prev + 1); // prev is 2 → returns 3
}
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>+3</button>
</div>
);
}
// User clicks the button:
// count becomes 3 (correct!)
//
// Each function receives the LATEST pending state, not the stale render value.
// React chains them: 0 → 1 → 2 → 3
The rule: If your next state depends on the previous state, always use the functional form setState(prev => ...). If you're setting a completely new value that doesn't depend on the old one (like setName("Alice")), the direct form is fine.
When Functional Updates Matter Most — Stale Closures
function Timer() {
const [seconds, setSeconds] = useState(0);
function startTimer() {
setInterval(() => {
// BUG: `seconds` is captured from the closure — always 0
// setSeconds(seconds + 1); // This would set it to 1 every time
// FIX: Use functional update to always get the latest value
setSeconds(prev => prev + 1);
}, 1000);
}
return (
<div>
<p>Seconds: {seconds}</p>
<button onClick={startTimer}>Start</button>
</div>
);
}
// Without functional update: 0 → 1 → 1 → 1 → 1 (stuck!)
// With functional update: 0 → 1 → 2 → 3 → 4 (correct!)
State vs Derived Values
Not everything needs to be state. If a value can be calculated from existing state or props, it's a derived value — and it should NOT be stored in state.
Code Example 4: State vs Derived
// BAD: Storing a derived value in state
function Cart({ items }) {
const [total, setTotal] = useState(0);
// Now you have to keep `total` in sync with `items` manually
// This is fragile, error-prone, and creates bugs
useEffect(() => {
setTotal(items.reduce((sum, item) => sum + item.price, 0));
}, [items]);
return <p>Total: ${total}</p>;
}
// GOOD: Calculate it during render — no extra state needed
function Cart({ items }) {
// `total` is derived from `items` — just calculate it
const total = items.reduce((sum, item) => sum + item.price, 0);
return <p>Total: ${total}</p>;
}
// The good version:
// - No extra state to manage
// - No useEffect to keep things in sync
// - No risk of `total` and `items` getting out of sync
// - Simpler, fewer bugs
The rule of thumb: If you can compute it from existing state or props, don't put it in state. State is for values that change over time and can't be derived from anything else.
Common derived values (should NOT be state):
- Filtered/sorted lists from an existing array
- Full name from first name + last name
- Total price from a list of items
- Whether a form is valid (derived from field values)
Common Mistakes
Mistake 1: Logging state right after setting it
const [name, setName] = useState("Alice");
function handleClick() {
setName("Bob");
console.log(name); // Output: "Alice" — not "Bob"!
}
// State updates are batched and applied on the next render.
// The `name` variable in this closure still holds the old value.
Mistake 2: Using direct updates when previous state matters
// WRONG — can produce stale results when called rapidly
setCount(count + 1);
// RIGHT — always gets the latest value
setCount(prev => prev + 1);
Any time your update depends on the current state — counters, toggles, appending to arrays — use the functional form.
Mistake 3: Storing derived values in state
If you find yourself writing a useEffect just to keep one state variable in sync with another, that's a sign the second variable should be a derived value, not state. Remove the extra state and calculate it inline during render.
Interview Questions
Q: What is state in React, and why can't we use regular variables instead?
Q: What is the output of this code?
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
}
The count becomes 1, not 3. All three calls read
countas 0 (the value from the current render). They all schedule "set to 1". React batches them and re-renders once with count = 1.
Q: What are functional updates in useState, and when should you use them?
Functional updates use the form
setState(prev => newValue). You should use them whenever the new state depends on the previous state — counters, toggles, array pushes, or any update inside a closure (setTimeout, setInterval) where the state value might be stale.
Q: What is state batching in React 18?
Q: How do you decide if a value should be state or a derived value?
If the value can be computed from existing state or props, it's derived — calculate it during render. If it represents something that changes independently over time (user input, toggle status, fetched data), it belongs in state.
Quick Reference — Cheat Sheet
+-----------------------------------+-------------------------------------------+
| Concept | Key Point |
+-----------------------------------+-------------------------------------------+
| useState(initialValue) | Returns [value, setter]. Initial value |
| | used only on first render. |
+-----------------------------------+-------------------------------------------+
| State vs variable | Variables reset on re-render. State |
| | persists and triggers re-render on change.|
+-----------------------------------+-------------------------------------------+
| Direct update | setCount(count + 1) |
| setCount(newValue) | Uses the value from current render. |
+-----------------------------------+-------------------------------------------+
| Functional update | setCount(prev => prev + 1) |
| setCount(prev => ...) | Uses the latest pending state. Safe for |
| | dependent updates & closures. |
+-----------------------------------+-------------------------------------------+
| Batching (React 18+) | All setState calls batched into one |
| | re-render — even in promises/timeouts. |
+-----------------------------------+-------------------------------------------+
| Derived value | Computed from state/props during render. |
| | Don't store in state. |
+-----------------------------------+-------------------------------------------+
RULE: If it depends on previous state → functional update.
RULE: If it can be computed → derive it, don't store it.
Previous: Lesson 2.1 — Props — Passing Data Down → Next: Lesson 2.3 — Lifting State Up & Prop Drilling →
This is Lesson 2.2 of the React Interview Prep Course — 10 chapters, 42 lessons.