How to Use useState in React
What you’ll build or solve
You’ll add state to a React component and update it correctly.
When this approach works best
Use this approach when you need to:
Learn React on Mimo
- Track simple values like counters, toggles, or input text.
- Update UI based on user interaction.
- Store temporary UI state inside a component.
Avoid using local state for data that should be shared across many unrelated components. In those cases, lift state up or use a different state management pattern.
Prerequisites
- A React app set up
- Basic understanding of functions and JSX
Step-by-step instructions
1) Initialize state and update with a direct value
Import useState and call it at the top level of your component.
JavaScript
import {useState }from"react";
exportdefaultfunctionCounter() {
const [count,setCount]=useState(0);
return (
<div>
<p>{count}</p>
<buttononClick={() =>setCount(5)}>Set to 5</button>
</div>
);
}
useState(0)sets the initial value.setCount(5)replaces the current state with a new value.
Use direct updates when the next value does not depend on the previous one.
What to look for:
- Always call
useStateat the top level, not inside conditions or loops. - The setter replaces the previous value entirely.
2) Use the functional updater when depending on previous state
When the next state depends on the previous one, use the functional form.
JavaScript
import {useState }from"react";
exportdefaultfunctionCounter() {
const [count,setCount]=useState(0);
functionincrement() {
setCount(prev =>prev+1);
}
return (
<div>
<p>{count}</p>
<buttononClick={increment}>Increment</button>
</div>
);
}
The function receives the latest state value as prev.
Use this pattern when:
- Updating based on the previous value.
- Triggering multiple updates in one event.
- Avoiding stale values inside closures.
Examples you can copy
Example 1: Toggle visibility
JavaScript
import {useState }from"react";
exportdefaultfunctionToggle() {
const [open,setOpen]=useState(false);
return (
<div>
<buttononClick={() =>setOpen(prev =>!prev)}>
Toggle
</button>
{open&&<p>Now you see me</p>}
</div>
);
}
The functional updater prevents incorrect toggling if updates happen quickly.
Example 2: Controlled input field
import {useState }from"react";
exportdefaultfunctionNameInput() {
const [name,setName]=useState("");
return (
<input
value={name}
onChange={e =>setName(e.target.value)}
placeholder="Type your name"
/>
);
}
Each change replaces the previous state directly.
Example 3: Counter with multiple updates
JavaScript
import {useState }from"react";
exportdefaultfunctionDoubleIncrement() {
const [count,setCount]=useState(0);
functionincreaseTwice() {
setCount(prev =>prev+1);
setCount(prev =>prev+1);
}
return (
<div>
<p>{count}</p>
<buttononClick={increaseTwice}>
+2
</button>
</div>
);
}
Using the functional updater guarantees both increments apply correctly.
Common mistakes and how to fix them
Mistake 1: Mutating objects or arrays directly
What you might do:
const [todos,setTodos]=useState([]);
functionaddTodo() {
todos.push("New task");
setTodos(todos);
}
Why it breaks:
React does not detect changes when you mutate the same array reference.
Fix:
functionaddTodo() {
setTodos(prev => [...prev,"New task"]);
}
Always return a new array or object instead of mutating the existing one.
Mistake 2: Not using functional updates when needed
What you might do:
setCount(count+1);
setCount(count+1);
Why it breaks:
Both calls use the same stale value of count.
Fix:
setCount(prev =>prev+1);
setCount(prev =>prev+1);
Each update now uses the latest value.
Mistake 3: Calling hooks conditionally
What you might do:
if (loggedIn) {
const [count,setCount]=useState(0);
}
Why it breaks:
Hooks must run in the same order on every render.
Fix:
Always declare hooks at the top level of your component.
const [count,setCount]=useState(0);
Troubleshooting
- If state does not update visually, check that you are not mutating objects or arrays.
- If multiple updates behave strangely, switch to the functional updater.
- If React throws a hooks error, confirm
useStateis not inside a condition or loop. - If the initial state calculation is expensive, use a lazy initializer:
const [value,setValue]=useState(() =>computeInitialValue());
Quick recap
- Call
useStateat the top level of your component. - Use direct updates when not depending on previous state.
- Use the functional updater when you are.
- Never mutate arrays or objects directly.
- Hooks must always run in the same order.
Join 35M+ people learning for free on Mimo
4.8 out of 5 across 1M+ reviews
Check us out on Apple AppStore, Google Play Store, and Trustpilot