Manage React component state with useState to store and update values across renders.
const [count, setCount] = useState(0);function Counter() {
const [count, setCount] = useState(0);
return (
<>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</>
);
}const [name, setName] = useState("");
const [isOpen, setIsOpen] = useState(false);
const [items, setItems] = useState([]);
const [user, setUser] = useState(null);// Calling setCount replaces the entire value
setCount(42); // count is now 42, not 42 + oldCountfunction Form() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [age, setAge] = useState(0);
return (
<>
<input value={name} onChange={(e) => setName(e.target.value)} />
<input value={email} onChange={(e) => setEmail(e.target.value)} />
<input value={age} onChange={(e) => setAge(e.target.value)} />
</>
);
}const [form, setForm] = useState({
name: "",
email: "",
age: 0
});const handleChange = (field) => (e) => {
setForm(prev => ({ ...prev, [field]: e.target.value }));
};// These are unrelated — keep them separate
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const [data, setData] = useState(null);// If isLoading, error, and data always change at once
// consider useReducer instead of three useState callsfunction Counter() {
const [count, setCount] = useState(0);
// Direct update (fine for simple cases)
// setCount(count + 1);
// Functional update (preferred for dependent updates)
return <button onClick={() => setCount(prev => prev + 1)}>Increment</button>;
}const handleClick = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
// Both increments use the latest state — count increases by 2
};useEffect(() => {
const timer = setInterval(() => {
setCount(prev => prev + 1); // safe inside interval
}, 1000);
return () => clearInterval(timer);
}, []); // no count in deps neededfunction addItem(newItem) {
setItems(prev => [...prev, newItem]);
}
function removeItem(id) {
setItems(prev => prev.filter(item => item.id !== id));
}// setName doesn't need the previous name
setName("Alice");
setIsOpen(true);const [state, setState] = useState(() => {
return expensiveComputation();
});const [todos, setTodos] = useState(() => {
const saved = localStorage.getItem("todos");
return saved ? JSON.parse(saved) : [];
});// Bad: expensiveComputation() runs on every render
const [state, setState] = useState(expensiveComputation());
// Good: arrow function defers the call to mount only
const [state, setState] = useState(() => expensiveComputation());// Correct: pass a function
useState(() => computeInitialValue())
// Wrong: calls the function immediately
useState(computeInitialValue())const [user, setUser] = useState({ name: "Alice", age: 30 });
// Wrong: mutation — React won't detect the change
user.age = 31;
// Right: create new object with spread
setUser({ ...user, age: 31 });const [items, setItems] = useState(["a", "b", "c"]);
// Add item
setItems([...items, "d"]);
// Remove item
setItems(items.filter(item => item !== "b"));const [profile, setProfile] = useState({ name: "Alice", address: { city: "NYC" } });
// Update nested field
setProfile(prev => ({
...prev,
address: { ...prev.address, city: "LA" }
}));const [tasks, setTasks] = useState([{ id: 1, done: false }]);
function toggleTask(id) {
setTasks(prev =>
prev.map(t => t.id === id ? { ...t, done: !t.done } : t)
);
}import produce from "immer";
setProfile(produce(draft => {
draft.address.city = "LA"; // direct mutation is safe inside produce
}));Use functional updates whenever the new state depends on the previous state, especially inside event handlers, async callbacks, or effects where the closure may capture a stale value. This guarantees you're working with the latest state, not a snapshot from when the function was created.
Spread the existing object and override only the changed fields: setState(prev => ({ ...prev, name: 'new' })). React requires a new object reference to detect the change, so never do setState(obj.name = 'new') directly.
Yes — declare as many useState calls as you need, one per logical piece of state. Keeping state variables separate (e.g., const [name, setName] and const [age, setAge]) is cleaner and avoids the need to spread on every update compared to storing everything in one object.
Passing a function to useState — useState(() => expensiveCalc()) — tells React to call it only on the initial render instead of every render. Use this when the initial value requires heavy computation, parsing, or reading from localStorage.
Calling array.push() mutates the existing array, so React sees the same reference and skips the re-render. Instead, create a new array: setState(prev => [...prev, newItem]).