State with useState
State is data that changes over time within a component. The useState hook lets you add state to function components.
Basic Syntax
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return <p>Count: {count}</p>;
}
useState returns an array: the current state value and a setter function.
Updating State
Call the setter to trigger a re-render:
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
React re-renders the component with the new value.
State vs Variables
A regular variable resets on every render:
function Bad() {
let x = 0;
x = x + 1; // Resets to 0 next render
return <p>{x}</p>;
}
function Good() {
const [x, setX] = useState(0);
setX(x + 1); // Persists across renders
return <p>{x}</p>;
}
State persists. Variables don’t.
State is Asynchronous
React batches state updates. The state variable doesn’t update immediately:
function Demo() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
console.log(count); // Still 0!
}
return <button onClick={handleClick}>{count}</button>;
}
The console.log shows the old value because state updates are batched.
Functional Updates
When the new state depends on the previous state, use a function:
setCount(prev => prev + 1);
setCount(prev => prev + 1);
// Both work correctly — prev is always current
Without the function, both reads use the same stale count.
Lazy Initialization
Pass a function to useState for expensive initial computations:
const [data, setData] = useState(() => {
return JSON.parse(localStorage.getItem("saved"));
});
The function only runs on the first render.
Best Practices
- Use functional updates — When new state depends on old state
- Keep state minimal — Derive values instead of duplicating
- Use one state object — For related values:
useState({ name, age }) - Initialize with lazy values — For expensive computations
Common Mistakes
- Mutating state directly —
state.value = xwon’t trigger re-render - Assuming state updates are synchronous — They’re batched and async
- Forgetting the setter —
const [x] = useState(0)— no way to update - Overusing useState — Combine related values into one state object