State with useState
State = Memory That Triggers Renders
Regular variables reset on every render and don’t update the screen. State persists between renders, and setting it re-renders the component:
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0); // [value, setter], 0 = initial
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}Click → setCount → React re-runs Counter with the new value → the DOM updates. That’s the entire reactive loop.
The Rules of Hooks
- Call hooks only at the top level of a component — never inside ifs, loops, or nested functions.
- Call them only from components or custom hooks.
React tracks hooks by call order; conditions would scramble it.
State Updates Are Asynchronous Snapshots
function handleClick() {
setCount(count + 1);
setCount(count + 1); // still the OLD count — both set the same value!
}
// updater function — receives the latest value
function handleClick() {
setCount(c => c + 1);
setCount(c => c + 1); // now +2 works
}Rule: when the new state depends on the old, pass a function.
Objects & Arrays — Always Replace, Never Mutate
React detects changes by comparing references. Mutating the existing object looks like “no change”:
const [user, setUser] = useState({ name: "Ravi", score: 0 });
// WRONG — same object, React may not re-render
user.score = 10;
setUser(user);
// RIGHT — new object via spread
setUser({ ...user, score: 10 });const [todos, setTodos] = useState([]);
setTodos([...todos, newTodo]); // add
setTodos(todos.filter(t => t.id !== id)); // remove
setTodos(todos.map(t =>
t.id === id ? { ...t, done: !t.done } : t // update one
));These three array patterns are half of all React state code you’ll ever write.
Multiple State Variables
const [query, setQuery] = useState("");
const [page, setPage] = useState(1);
const [isOpen, setIsOpen] = useState(false);Split unrelated state; group fields that change together into one object.
Lifting State Up
When two components need the same data, move the state to their closest common parent and pass it down:
function App() {
const [query, setQuery] = useState("");
return (
<>
<SearchBox query={query} onChange={setQuery} />
<ResultList query={query} />
</>
);
}The child calls onChange(newValue); the parent owns the truth. State down, events up — the fundamental React data flow.