The useState Hook
useState is the React hook for adding state to a function component.
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}useState(0) creates state with an initial value of 0.
It returns an array with two items:
- the current state value
- a setter function that requests an update
Naming Convention
The common pattern is:
const [value, setValue] = useState(initialValue);Examples:
const [isOpen, setIsOpen] = useState(false);
const [email, setEmail] = useState("");
const [selectedId, setSelectedId] = useState(null);Use names that describe the UI state, not generic names like data when a clearer name exists.
Initial Values
Choose an initial value that matches how the component renders before user interaction.
const [query, setQuery] = useState("");
const [items, setItems] = useState([]);
const [error, setError] = useState(null);Avoid switching between unrelated types unless the state truly has multiple states.
// Harder to reason about
const [items, setItems] = useState(null);
// Often easier when the UI expects an array
const [items, setItems] = useState([]);Lazy Initial State
If the initial value is expensive to calculate, pass a function.
function PreferencesPanel() {
const [settings, setSettings] = useState(() => {
const saved = localStorage.getItem("settings");
return saved ? JSON.parse(saved) : { theme: "light" };
});
return <p>Theme: {settings.theme}</p>;
}React calls the initializer only for the initial render.
Do not call the function yourself:
// Runs on every render before useState receives the value
const [settings, setSettings] = useState(loadSettings());
// Runs once for initial state
const [settings, setSettings] = useState(loadSettings);Setter Functions Request Updates
Calling a setter tells React to render with a new value.
setCount(count + 1);It does not change the current count variable in the running function. The new value appears in the next render.
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
console.log(count); // old value from this render
}
return <button onClick={handleClick}>{count}</button>;
}Functional Updates
When the next state depends on the previous state, use a functional update.
setCount((currentCount) => currentCount + 1);This is especially important when multiple updates happen together.
function AddThreeButton() {
const [count, setCount] = useState(0);
function addThree() {
setCount((count) => count + 1);
setCount((count) => count + 1);
setCount((count) => count + 1);
}
return <button onClick={addThree}>Count: {count}</button>;
}Each updater receives the latest queued value.
Hook Rules Preview
Hooks must be called:
- at the top level of a component or custom hook
- in the same order on every render
- not inside conditions, loops, or nested functions
// Bad
if (isLoggedIn) {
const [draft, setDraft] = useState("");
}React relies on call order to connect state values to hook calls.
Common Mistakes
- Forgetting to import
useState. - Calling
useStateconditionally. - Expecting the state variable to update immediately after calling its setter.
- Passing
loadInitialState()instead ofloadInitialStatefor expensive initial state. - Using
setCount(count + 1)several times in one handler when a functional update is needed.
When should you prefer setCount((count) => count + 1)?
Practice Challenge
Build a Stepper component.
Requirements:
- starts at
0 - has
+1,-1, andResetbuttons - uses functional updates for increment and decrement
- disables the decrement button when the value is
0
Recap
useState gives a component remembered state and a setter. Choose clear initial values, use lazy initialization for expensive setup, and use functional updates when the next state depends on the previous state.