The useState hook is one of React's core hooks that enables state management in functional components. State allows your components to "remember" data between renders and respond to user interactions.
This guide covers:
useState
worksuseState
effectivelyState represents data that changes over time in your application. Unlike props (which are passed from parent components), state is managed within the component itself.
graph TD
A[Component Renders] --> B[User Interacts]
B --> C[State Changes]
C --> D[Component Re-renders]
D --> B
State is crucial for:
📖 Read more: Thinking in React - Identify State
The useState
hook is a function that:
graph LR
A[useState] --> B[State Variable]
A --> C[Update Function]
C --> D[Update State]
D --> E[Trigger Re-render]
E --> B
The useState
hook is used to create a state variable with an initial value. It returns an array containing the state variable and a function to update it.
const [state, setState] = useState(initialValue);
💡 Note: The
state
andsetState
are just values that you can name whatever you want. The convention is<variableName>
,<"set" + VariableName>
. See With Different Data Types for examples.
initialValue
: The value when the component first rendersstate
: The current state valuesetState
: The function to update the state value🎬 Watch: React useState Hook in 15 Minutes
Use useState
when:
function Counter() {
// ✅ Good use of useState - local counter state
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
</div>
);
}
// Number
const [count, setCount] = useState(0);
// String
const [name, setName] = useState('');
// Boolean
const [isActive, setIsActive] = useState(false);
// Array
const [items, setItems] = useState([]);
// Object
const [user, setUser] = useState({ name: '', age: 0 });
// null
const [selectedItem, setSelectedItem] = useState(null);
If your initial state requires expensive computation, use a function inside useState
to ensure it only runs once:
// ❌ Bad: This runs on every render
const [items, setItems] = useState(calculateExpensiveInitialState());
// ✅ Good: This runs only on first render
const [items, setItems] = useState(() => calculateExpensiveInitialState());
📖 Read more: Avoiding recreating the initial state
For simple values like numbers, strings, and booleans:
const [count, setCount] = useState(0);
// Update to specific value
function resetCount() {
setCount(0);
}
// Update based on current value
function increment() {
setCount(count + 1);
}
When the new state depends on the previous state, use the functional form:
// ❌ May lead to outdated state in some scenarios
function increment() {
setCount(count + 1);
}
// ✅ Always uses latest state
function increment() {
setCount(prevCount => prevCount + 1);
}
📖 Read more: Updating state based on the previous state
React state updates are not merged automatically. For objects, you must preserve existing fields:
const [user, setUser] = useState({ name: 'John', age: 25 });
// ❌ Bad: Will remove the age property
function updateName(newName) {
setUser({ name: newName });
}
// ✅ Good: Preserves other properties
function updateName(newName) {
setUser(prevUser => ({ ...prevUser, name: newName }));
}
Use array methods that return new arrays rather than mutating existing ones:
const [items, setItems] = useState([1, 2, 3]);
// ✅ Add item
setItems([...items, 4]);
// ✅ Remove item
setItems(items.filter(item => item !== 2));
// ✅ Update item
setItems(items.map(item => item === 2 ? 20 : item));
📖 Read more: Updating Arrays in State
// ❌ Bad: Mutating state directly won't trigger re-renders
function addTodo(newTodo) {
todos.push(newTodo); // This won't work!
}
// ✅ Good: Create new array
function addTodo(newTodo) {
setTodos([...todos, newTodo]);
}
State updates are batched and asynchronous. Don't rely on state being updated immediately after setting it.
// ❌ Won't work as expected
function increment() {
setCount(count + 1);
console.log(count); // Still has the old value!
}
// ✅ Use useEffect to respond to state changes
useEffect(() => {
console.log("Count updated:", count);
}, [count]);
React may batch multiple state updates for performance. Use functional updates to ensure correct sequencing.
// ❌ May only increment once if updates are batched
function incrementThrice() {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
}
// ✅ Will correctly increment three times
function incrementThrice() {
setCount(c => c + 1);
setCount(c => c + 1);
setCount(c => c + 1);
}
When you have multiple related state values, consider using a single object with useState
or switching to useReducer.
// ❌ Too many useState calls for related data
const [username, setUsername] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [isValid, setIsValid] = useState(false);
const [errors, setErrors] = useState({});
// ✅ Better: Group related state
const [form, setForm] = useState({
username: '',
email: '',
password: '',
isValid: false,
errors: {}
});
// Or even better for complex forms: useReducer.
📖 Read more: Choosing the State Structure