React useState Hook
State is data that can change over time inside a component. When state changes, React re-renders the component so the UI stays in sync with the data.
useState is the hook you reach for when a component needs to remember something between renders: a counter value, a text input’s current value, whether a modal is open, a list of items, and so on.
See React State for a broader overview of how state works in React.
How useState works
useState takes an initial value as its argument and returns an array with two items:
- The current state value
- A function to update it
import { useState } from 'react';
const [count, setCount] = useState(0);
// ^^^^^ ^^^^^^^^ ^
// value setter initial value
The square bracket syntax is JavaScript array destructuring. You can name the two values anything you like, but the convention is value and setValue.
When you call the setter function, React updates the state and re-renders the component with the new value.
Examples
Basic counter
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Every time the button is clicked, setCount is called with a new value. React re-renders Counter with the updated count.
Updating based on previous state
When the new value depends on the old value, pass a function to the setter instead of a value. React guarantees the function receives the most recent state.
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<h2>{count}</h2>
<button onClick={() => setCount(prev => prev - 1)}>Decrease</button>
<button onClick={() => setCount(0)}>Reset</button>
<button onClick={() => setCount(prev => prev + 1)}>Increase</button>
</div>
);
}
Always use the functional form setCount(prev => prev + 1) when the new value depends on the old one. This prevents bugs that occur when multiple state updates happen in the same event.
Array state
When state is an array, you must return a new array instead of mutating the existing one. Common patterns use filter, map, and the spread operator.
import { useState } from 'react';
const initialPeople = [
{ id: 1, name: 'Anna' },
{ id: 2, name: 'Joe' },
{ id: 3, name: 'Pan' },
];
export default function PeopleList() {
const [people, setPeople] = useState(initialPeople);
const removeItem = (id) => {
setPeople(current => current.filter(person => person.id !== id));
};
return (
<div>
{people.map(person => (
<div key={person.id}>
<span>{person.name}</span>
<button onClick={() => removeItem(person.id)}>Remove</button>
</div>
))}
<button onClick={() => setPeople([])}>Clear all</button>
</div>
);
}
Object state
When state is an object, spread the existing values and override only the ones that changed.
import { useState } from 'react';
export default function Profile() {
const [person, setPerson] = useState({
name: 'Anna',
age: 20,
message: 'Hello there',
});
const updateMessage = () => {
setPerson(current => ({ ...current, message: 'Message updated!' }));
};
return (
<div>
<p>{person.name}, age {person.age}</p>
<p>{person.message}</p>
<button onClick={updateMessage}>Update message</button>
</div>
);
}
The spread operator (...current) copies all existing properties. Then message: 'Message updated!' overrides just the one you want to change. Without the spread, you would lose all the other properties.
Lazy initialisation
If the initial state value is expensive to compute (reading from localStorage, doing a calculation), pass a function instead of a value. React will only call that function once, on the first render.
// This runs the function on every render (wasteful if it's slow)
const [data, setData] = useState(expensiveCalculation());
// This only runs the function once, on mount
const [data, setData] = useState(() => expensiveCalculation());
TypeScript
If you are using TypeScript, you can annotate the type of your state:
const [name, setName] = useState<string>('');
const [items, setItems] = useState<string[]>([]);
const [user, setUser] = useState<User | null>(null);
TypeScript will then catch any attempt to set a value of the wrong type.
Common mistakes
Mutating state directly
React only knows to re-render when you call the setter function. If you modify state directly, nothing updates.
// Wrong: React does not know the array changed
people.push({ id: 4, name: 'Lee' });
// Correct: replace the array with a new one
setPeople(current => [...current, { id: 4, name: 'Lee' }]);
This applies to objects too. Do not do person.name = 'New Name'. Use the setter with a spread instead.
Assuming state updates are synchronous
Calling a setter does not immediately change the state variable. The component re-renders first, and then the new value is available.
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
console.log(count); // Still logs the old value
}
If you need to work with the updated value, do it in the next render, or calculate it from the current value directly.
Using the value instead of the functional update
If you call a setter multiple times in the same event handler, each call reads the same snapshot of state:
// Both calls see count = 0, so count ends up as 1, not 2
setCount(count + 1);
setCount(count + 1);
// Both calls chain correctly, so count ends up as 2
setCount(prev => prev + 1);
setCount(prev => prev + 1);
Use the functional form any time you call the setter more than once per event, or when you are not certain you have the latest value.
FAQ
Why isn’t my state updating right away?
State updates in React are batched and asynchronous. When you call setCount(1), React schedules a re-render. The actual update happens before the next render, not on the same line you called the setter. If you need to react to a state change, use useEffect with that state value as a dependency.
When should I use useState vs useReducer?
Use useState when you have one or two simple values that update independently. Use useReducer
when you have several pieces of state that update together, when the next state depends on the previous one in a complex way, or when your update logic has grown hard to follow with multiple setState calls.
Can I store objects or arrays in state?
Yes. The important rule is that you must always replace the value with a new object or array rather than modifying the existing one. React compares state by reference. If you mutate an array or object in place, React sees the same reference and skips the re-render.
What to learn next
- useReducer : managing more complex state
- useEffect : running code when state changes
- React State overview : conceptual explanation of state in React