React useState Hook
Written By: Avinash Malhotra
Updated on
useState hook is a built-in React hook that allows you to add state management to functional components. It provides a way to declare state variables and update them within your component. Change detection is done automatically by React by comparing the current state with the previous state.
Since Hooks were introduced (React 16.8), use the useState Hook to create and manage state in function components.
Here count is a variable and setCount is a function which triggers a re-render of the component.
useState bases counter
// App.jsx
import {useState} from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<h2>You clicked {count} times</h2>
<button onClick={() => setCount(prev => prev + 1)}>Click Me</button>
</div>
);
}
export default Counter;
countis the state variable.setCountis the function to update the state.useState(0)sets the initial value ofcountto 0.
Updating State Correctly
Never update state directly. Always use the setter function returned by useState. When an update depends on previous state, prefer the functional updater form to avoid stale values.
{`
/ ❌ Wrong
count = count + 1;
// ✅ Correct (simple)
setCount(count + 1);
// ✅ Recommended when new state depends on previous state
setCount(prev => prev + 1);
`}
Queuing State Updates
React batches state updates for performance. If you call the state updater function multiple times in the same event, React will batch them into a single re-render.
{`
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
setCount(count + 2);
setCount(count + 3);
};
return (
<div>
<h2>You clicked {count} times</h2>
<button onClick={handleClick}>Click Me</button>
</div>
);
}
`}
In the above example, clicking the button will only increase the count by 3, not 6, because all updates are based on the same initial value of count.
To ensure each update is based on the latest state, use the functional updater form:
{`function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 2);
setCount(prev => prev + 3);
};
return (
<div>
<h2>You clicked {count} times</h2>
<button onClick={handleClick}>Click Me</button>
</div>
);
}
`}
Now, clicking the button will correctly increase the count by 6.
Multiple State Variables
You can use multiple useState hooks in one component. For example: use one state to track the user's name and another for their age.
{`function Profile() {
const [name, setName] = useState("");
const [age, setAge] = useState(30);
return (
<div>
<p>Name: {name}</p>
<p>Age: {age}</p>
<input type="text" value={name} onInput={(e) => setName(e.target.value)} />
<input type="number" value={age} onInput={(e) => setAge(Number(e.target.value))} />
</div>
);
}`}
Common Mistakes with State
Here are some common mistakes to avoid when using state.
- Do not update state directly.
- Do not rely on old state without using the updater function.
- State updates are asynchronous, so plan accordingly.
Objects in State
When using objects in state, remember that state updates must be immutable. Create a new object with all properties you want to keep.
Never modify the existing state object directly.
{`function UserForm() {
const [user, setUser] = useState({ name: '', email: '' });
// ❌ Wrong: mutating state directly
const handleBadUpdate = () => {
user.name = 'John'; // Don't modify state object
setUser(user);
}
// ✅ Correct: create new object with all properties
const handleGoodUpdate = (name) => {
setUser({ ...user, name }); // Spread existing properties
}
return (
<form>
<input
value={user.name}
onChange={(e) => handleGoodUpdate(e.target.value)}
/>
</form>
);
}`}Handle multiple properties
When updating multiple properties, always spread the existing state to retain unchanged properties.
For example:
{`// Updating multiple properties
setUser(prev => ({
...prev, // Keep existing properties
name: 'Jane', // Update name
email: 'jane@example.com' // Update email
}));`}Using single event handler
You can use a single event handler to update different properties based on input name attributes.
name and email are used as the input's name attributes.
{`function UserForm() {
const [user, setUser] = useState({ name: '', email: '' });
// Single handler for multiple inputs
const handleChange = (e) => {
const { name, value } = e.target;
setUser(prev => ({ ...prev, [name]: value }));
};
return (
<form>
<input name="name" value={user.name} onChange={handleChange} />
<input name="email" value={user.email} onChange={handleChange} />
</form>
);
}`}Arrays in State
When working with arrays in state, use array methods that return new arrays instead of mutating the original. This ensures React can detect changes and re-render the component.
For example, use map, filter, and concat instead of mutating methods like push or splice.
Always return a new array when updating state.
export Default function arrayData() {
const [nums,setNums]=useState([1,2,3,4,5]);
console.log( nums );
const addNums=()=>{
setNums([...nums,nums.length+1]);
}
const removeNums = () => {
setNums(nums.filter(i => i % 2 !== 0));
};
const updateNums = () => {
setNums(nums.map(i => i * 2));
};
return (
<div>
<h2>Array State Example</h2>
<button onClick={addNums}>Add Number</button>
<button onClick={removeNums}>Remove Even Numbers</button>
<button onClick={updateNums}>Double Numbers</button>
<ul>
{nums.map(num => <li key={num}>{num}</li>)}
</ul>
</div>
);
Another Example
{`function TodoList() {
const [todos, setTodos] = useState(['Learn React']);
// Adding items
const addTodo = (text) => {
setTodos([...todos, text]); // Create new array
};
// Removing items
const removeTodo = (index) => {
setTodos(todos.filter((_, i) => i !== index));
};
// Updating items
const updateTodo = (index, newText) => {
setTodos(
todos.map((todo, i) => (i === index ? newText : todo))
);
};
}`}State Updates and Batching
React batches state updates for performance. If you need to update state multiple times based on the same value, use the updater function.
{`function Counter() {
const [count, setCount] = useState(0);
// ❌ These will batch and you'll only get +1
const handleBadClick = () => {
setCount(count + 1);
setCount(count + 1);
};
// ✅ These will properly add +2
const handleGoodClick = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
};
}`}State Management Patterns
Effective state management is crucial for building scalable React applications. Here are some common patterns and best practices for managing state effectively:
Lifting State Up
Lifting state up is a common pattern in React where you move state to a common ancestor component when multiple child components need access to it. This allows you to share state between components more easily.
{`// Parent Component
function Parent() {
const [sharedState, setSharedState] = useState(0);
return (
<div>
<ChildA state={sharedState} setState={setSharedState} />
<ChildB state={sharedState} />
</div>
);
}
// ChildA Component
function ChildA({ state, setState }) {
return <button onClick={() => setState(state + 1)}>Increment</button>;
}
// ChildB Component
function ChildB({ state }) {
return <div>State: {state}</div>;
}`}In this example, the state is lifted to the Parent component, allowing both ChildA and ChildB to access and modify it.
Derived State
Derived state is state that can be calculated from other state or props. Instead of storing derived state, calculate it during render. For example, if you have a list of items and want to display the total count, you can calculate it on the fly:
{`const items = [{ price: 10 }, { price: 20 }, { price: 30 }];
const total = items.reduce((sum, item) => sum + item.price, 0);
console.log(total); // 60`}
Avoid storing derived state unless absolutely necessary, as it can lead to inconsistencies and bugs.
For example, instead of storing a total in state that depends on an items array, calculate it during render:
{`// ❌ Redundant state
const [total, setTotal] = useState(0);
const [items, setItems] = useState([]);
// Every time items changes, update total
useEffect(() => {
setTotal(items.reduce((sum, item) => sum + item.price, 0));
}, [items]);
// ✅ Calculate during render instead
const total = items.reduce((sum, item) => sum + item.price, 0);`}Reset and Initialize Pattern
When you need to reset state to initial values, prefer passing a key prop to remount the component or use a reset function.
Practical Example: Todo List
Here's a complete example that combines multiple state concepts:
{`function TodoList() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');
const addTodo = (e) => {
e.preventDefault();
if (!input.trim()) return;
setTodos(prev => [...prev, {
id: Date.now(),
text: input,
completed: false
}]);
setInput('');
};
const toggleTodo = (id) => {
setTodos(prev => prev.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
));
};
const removeTodo = (id) => {
setTodos(prev => prev.filter(todo => todo.id !== id));
};
return (
<div>
<form onSubmit={addTodo}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Add todo"
/>
<button type="submit">Add</button>
</form>
<ul>
{todos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span style={{
textDecoration: todo.completed ? 'line-through' : 'none'
}}>
{todo.text}
</span>
<button onClick={() => removeTodo(todo.id)}>
Delete
</button>
</li>
))}
</ul>
</div>
);
}`}This example demonstrates:
- Managing multiple state variables
- Updating objects and arrays immutably
- Event handling and form input
- Conditional rendering
- List rendering with keys
- Derived values (completed styling)