Hello! Welcome back to our journey into React.
In the last lesson, we focused on a crucial pattern: updating state based on its previous value using an updater function, like setCount(prevCount => prevCount + 1). This ensures our state updates are always correct, especially when they happen in quick succession or asynchronously.
Today, we'll build on that foundation to manage more complex components. A real-world component rarely has just one piece of information to track. This lesson will teach you how to manage multiple state variables within a single component. We will explore the two primary strategies for this: using multiple useState calls for independent data, and grouping related data into a single state object.
1. The Recommended Approach: Multiple useState Calls
The most common and recommended way to manage multiple, unrelated pieces of state in a component is to simply call useState for each one. Each call to useState creates a completely independent "state slot" within your component.
Let's look at the official React documentation for a clear example of the syntax.
The official React documentation shows how cleanly you can declare multiple state variables at the top of your component.
In this resource, look at the first code block under the 'Reference' section. It demonstrates declaring three separate state variables: age, name, and todos. Notice how each one gets its own variable and setter function.
As you saw, you can declare as many state variables as you need. Each one is independent, and updating one will not affect the others.
import { useState } from 'react';
function UserProfile() {
const [name, setName] = useState('Alex');
const [isOnline, setIsOnline] = useState(false);
const [followerCount, setFollowerCount] = useState(150);
// ...logic to update these values
}
Let's see this in action with a more practical example. The following video builds a counter component where not only the count is state, but the increment step is also a piece of state that the user can change.
ReactJS Tutorial 24: Creating Multiple States In React
This video from Ashutosh Pawar provides an excellent step-by-step demonstration of creating and using two interacting state variables.
Watch from the beginning to 04:59. The video will guide you through: Identifying the need for a second state variable (incrementBy). Declaring the new state variable using another useState call. Building UI controls (buttons) to update this new state independently. Finally, modifying the original counter logic to use the incrementBy state, showing how two separate states can work together.
This example clearly shows the power of this approach. The count and incrementBy variables are managed separately, each with its own logic, but they can be combined to create dynamic behavior.
2. The Alternative: Grouping State in an Object
Sometimes, you'll have several pieces of state that are closely related. For example, a form with fields for firstName, lastName, and email. In these cases, it can be tempting to group them into a single state object:
const [form, setForm] = useState({
firstName: 'Barbara',
lastName: 'Hepworth',
email: 'bhepworth@sculpture.com',
});
This is a valid pattern, but it comes with a critical rule you must follow: the useState setter function replaces the state value. It does not automatically merge objects.
If you were to update only the first name like this, you would lose the lastName and email properties:
// 🚩 INCORRECT: This will delete lastName and email from the state!
function handleFirstNameChange(e) {
setForm({ firstName: e.target.value });
}
To update a state object correctly, you must create a new object that includes all the properties from the old object, and then overwrite the specific property you want to change. This is a perfect use case for the ES6 spread syntax (...).
The correct way to update the first name would be:
// ✅ CORRECT: Spreading the previous form state
function handleFirstNameChange(e) {
setForm({
...form, // Copy all existing fields
firstName: e.target.value // Overwrite just the one that changed
});
}
This next video explains this "replacement" behavior and the spread syntax solution very clearly.
Learn useState In 15 Minutes - React Hooks Explained
In this clip from Web Dev Simplified, you'll see a demonstration of what goes wrong when you don't spread the previous state object and how to fix it.
Watch from 11:17 to 14:50. The video first shows how an update to an object-based state accidentally removes other properties. Then, it introduces the spread syntax as the solution and concludes by recommending the multiple useState approach for unrelated state, which we've already covered.
To solidify this concept, let's review the form example from the official React documentation, which puts this pattern into practice.
This example shows how to manage a form's state using a single object, correctly applying the spread syntax for each update.
Find the section titled 'Updating objects and arrays in state'. Read the text and study the 'Form (object)' example. Pay close attention to how each onChange handler calls setForm, spreading the existing form object before setting the new value for its specific field.
Remember our last lesson? If an update depends on the previous state, we use an updater function. This is especially important for state objects. The safest way to write our update is:
function handleFirstNameChange(e) {
const newFirstName = e.target.value;
setForm(prevForm => ({
...prevForm,
firstName: newFirstName
}));
}
This combines the two key principles we've learned for safe and robust state updates.
3. Structuring State: Which Approach Should You Use?
So, we have two patterns: multiple useState calls and a single useState object. How do you decide which one to use? The React team provides clear guidance on this.
This is a design decision that balances code readability and performance.
useState in React: A complete guide - LogRocket Blog
The article 'useState in React: A complete guide' has an excellent section that breaks down this exact question.
Read the section titled 'Managing React state: Multiple variables vs. One state object'. It directly compares the two approaches and provides a crucial recommendation: split the state into multiple variables based on which values tend to change together.
Here is a summary of the guiding principles:
| Guiding Principle | Multiple useState Calls |
Single useState Object |
|---|---|---|
| Relatedness | Best for unrelated state (e.g., isLoading, error, data). |
Good for related state that often changes together (e.g., coordinates {x, y}). |
| Readability | Can be very clear. setIsLoading(true) is more explicit than setState({ ...s, isLoading: true }). |
Can group related data under one name, which might be cleaner for things like a user object. |
| Updates | Simple. Each piece of state has its own setter. | More complex. You must always remember to spread the previous state to avoid losing data. |
| Performance | Better for performance. Updating one state variable only re-renders components that depend on that specific variable. | Can cause unnecessary re-renders. A component that only needs form.firstName will still re-render if form.email changes. |
| Nesting | Not applicable. State is flat. | Avoid deeply nested objects. Updating them ({ ...s, nested: { ...s.nested, deep: 'val' } }) is verbose and error-prone. |
The general recommendation is to prefer splitting state into multiple useState variables. Start with that, and only group state into an object if you have a clear reason to, such as when multiple values are very tightly coupled and always update at the same time.
Conclusion
Congratulations! You now have a solid understanding of how to structure state in your React components, no matter how much information they need to manage.
Key Takeaways:
- You can call
useStatemultiple times in a component to create several independent state variables. This is the most common and generally recommended approach. - Alternatively, you can group related data into a single state object.
- Crucially, when updating a state object, you must create a new object by spreading the previous state (
...prevState) to avoid data loss, as the setter function replaces the state. - Structure your state based on how data changes. Group data that changes together; split data that changes independently.
- When in doubt, start by splitting state into multiple
useStatecalls.
Next Up:
We've mentioned that changing state causes a component to re-render. We've also hinted that how you structure your state can impact what re-renders. In our next lesson, we will dive deep into this topic and explain exactly when and why a component re-renders in response to state changes. This is fundamental to understanding React's performance and behavior.