βοΈ React Hook: useState
β
π Quick Summary β
useState
is a React hook that lets you add state variables to functional components.- State is data that changes over time and triggers a re-render when updated.
- Syntax:tsx
const [state, setState] = useState(initialValue);
π§ Mental Model β
- Think of state as a box π¦ inside your component that React watches.
- When you update the box using the setter (
setState
), React repaints the UI with the new data. - Updating state does not mutate the variable directly β it schedules a re-render.
π Key Concepts β
Initialization
tsxconst [count, setCount] = useState(0);
count
β current state valuesetCount
β function to update state0
β initial value
State Updates Trigger Re-renders
- Direct assignment (
count = 5
) wonβt re-render. - Must use setter (
setCount(5)
).
- Direct assignment (
Asynchronous Updates
- React may batch multiple state updates.
- Always use the functional form when the new state depends on the old:tsx
setCount(prev => prev + 1);
Lazy Initialization
- Pass a function for expensive initializations:tsx
const [value, setValue] = useState(() => computeExpensiveValue());
- Pass a function for expensive initializations:
Multiple State Variables
- You can call
useState
multiple times in a component. - Each call manages a separate piece of state.
- You can call
π» Code Examples β
Example 1: Basic Counter β
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(count - 1)}>Decrement</button>
</div>
);
}
How it works (stepβbyβstep):
- Component renders with
count = 0
(initial value). - When you click Increment, React calls
setCount(count + 1)
. - React schedules a re-render; on the next render,
count
becomes1
. - The
<p>
shows the updatedcount
. - Same flow for Decrement (value decreases and UI re-renders).
Example 2: Functional Updates β
import { useState } from "react";
export default function DoubleIncrement() {
const [count, setCount] = useState(0);
const handleDouble = () => {
// Ensures both increments see the latest value
setCount(prev => prev + 1);
setCount(prev => prev + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleDouble}>+2</button>
</div>
);
}
How it works (stepβbyβstep):
- Initial render with
count = 0
. - Click +2 β
handleDouble
runs twosetCount
calls. - Because we use the functional form, the 2nd update reads the value from the 1st update.
- React batches both updates in one tick β new state becomes
count + 2
. - Component re-renders with the final value.
Example 3: Managing Objects/Arrays β
import { useState } from "react";
export default function TodoList() {
const [todos, setTodos] = useState([{ id: 1, text: "Learn React" }]);
const addTodo = () => {
setTodos(prev => [...prev, { id: prev.length + 1, text: "New Task" }]);
};
return (
<>
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
<button onClick={addTodo}>Add Todo</button>
</>
);
}
How it works (stepβbyβstep):
todos
starts with one item.- On Add Todo, we create a new array using the spread (
[...]
) to avoid mutating state. - We append the new item with an incremented
id
. - React detects a new reference, re-renders, and the list shows the added item.
Example 4: Lazy Initialization β
function computeExpensiveValue() {
console.log("Expensive calculation running...");
return 42;
}
export default function ExpensiveComponent() {
const [value, setValue] = useState(() => computeExpensiveValue());
return <p>Expensive value: {value}</p>;
}
How it works (stepβbyβstep):
- On the very first render, React calls the initializer function once β
computeExpensiveValue()
. - The returned value
42
is stored as the initial state. - Subsequent re-renders do not re-run the expensive function.
- UI displays the current
value
.
β οΈ Common Pitfalls & Gotchas β
- β Updating state directly (
count = count + 1
) β wonβt re-render. - β Forgetting that updates are async β
console.log(count)
aftersetCount
shows old value. - β Overwriting object/array state instead of copying β use spread operator (
{...}
/[...arr]
). - β Using state inside loops/conditions β hooks must be called in the same order every render.
β Best Practices β
- Use descriptive names (
isOpen
,todos
,count
). - When new state depends on old state β always use functional updates.
- Group related values into objects, but keep unrelated values separate.
- Prefer lazy initialization for expensive calculations.
- Keep state as minimal as possible β derive values where you can.
β Interview Q&A β
Q1. What is the purpose of useState
in React?
A: It adds state to functional components. State is preserved between renders, and updating it triggers a re-render.
Q2. Why canβt we update state variables directly?
A: Direct assignment doesnβt notify React. Only calling the setter function (setState
) schedules a re-render.
Q3. Why use functional updates (setCount(prev => prev + 1)
)?
A: Because React batches state updates. Functional form ensures each update uses the latest value, avoiding stale state.
Q4. What is lazy initialization in useState
?
A: If initial value calculation is expensive, pass a function:
const [value, setValue] = useState(() => heavyComputation());
This function runs only once on initial render.
Q5. Can we use multiple useState
calls in a single component?
A: Yes. Each call manages independent state. Hooks are tracked in order, so calls must not be inside loops/conditions.
Q6. What happens when we update state in React?
A: React schedules a re-render. During reconciliation, the Virtual DOM is updated, then efficiently synced with the real DOM.
Q7. Difference between state and props?
- State: managed within a component, can be updated with
setState
. - Props: passed from parent to child, read-only in the child.