Real components rarely have just one piece of state. You might need to track a counter, form inputs, loading status, error messages, and more - all at the same time. Should you use one big object or multiple separate state variables? When should you split state and when should you combine it? In this lesson, you'll learn how to organize state effectively, making your components cleaner and easier to maintain.
Using Multiple useState Calls
You can call useState as many times as you need in a component:
function UserProfile() {
// Multiple independent state variables
const [name, setName] = React.useState('')
const [age, setAge] = React.useState(0)
const [email, setEmail] = React.useState('')
const [isActive, setIsActive] = React.useState(false)
const [notifications, setNotifications] = React.useState([])
return (
<div>
<input
value={name}
onChange={e => setName(e.target.value)}
placeholder="Name"
/>
<input
type="number"
value={age}
onChange={e => setAge(Number(e.target.value))}
placeholder="Age"
/>
<input
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
placeholder="Email"
/>
<label>
<input
type="checkbox"
checked={isActive}
onChange={e => setIsActive(e.target.checked)}
/>
Active
</label>
</div>
)
}
// Each state variable is independent
// Updating one doesn't affect the othersBenefits of Multiple State Variables:
- Each piece of state has a clear, descriptive name
- Easy to update individual values
- No need to spread previous state
- Clear what each setter function does
Using Single Object State
Alternatively, you can combine related data into a single state object:
function UserProfile() {
// Single state object
const [user, setUser] = React.useState({
name: '',
age: 0,
email: '',
isActive: false
})
const updateName = (e) => {
setUser({ ...user, name: e.target.value })
}
const updateAge = (e) => {
setUser({ ...user, age: Number(e.target.value) })
}
const updateEmail = (e) => {
setUser({ ...user, email: e.target.value })
}
const toggleActive = () => {
setUser({ ...user, isActive: !user.isActive })
}
return (
<div>
<input value={user.name} onChange={updateName} placeholder="Name" />
<input type="number" value={user.age} onChange={updateAge} placeholder="Age" />
<input type="email" value={user.email} onChange={updateEmail} placeholder="Email" />
<label>
<input
type="checkbox"
checked={user.isActive}
onChange={toggleActive}
/>
Active
</label>
</div>
)
}
// All related data in one object
// Need to spread previous state when updatingBenefits of Object State:
- Related data grouped together
- Easier to pass around as one unit
- Matches API response structure
- Fewer state variables to manage
When to Split State
✅ Split State When Values Are Independent
// ✅ Good - these are independent
function Dashboard() {
const [user, setUser] = React.useState(null)
const [theme, setTheme] = React.useState('light')
const [sidebarOpen, setSidebarOpen] = React.useState(false)
const [notifications, setNotifications] = React.useState([])
// User and theme are unrelated
// Sidebar state doesn't depend on notifications
// Each can change independently
}✅ Split State for Different Update Patterns
// ✅ Good - different update frequencies
function SearchPage() {
const [query, setQuery] = React.useState('') // Changes on every keystroke
const [results, setResults] = React.useState([]) // Changes after API call
const [isLoading, setIsLoading] = React.useState(false) // Changes before/after API
const [error, setError] = React.useState(null) // Changes on error
// These update at different times and for different reasons
// Keeping them separate makes logic clearer
}✅ Split State for Primitive Values
// ✅ Good - simple values
function Counter() {
const [count, setCount] = React.useState(0)
const [step, setStep] = React.useState(1)
const [isRunning, setIsRunning] = React.useState(false)
// Each is a simple value that updates independently
// No need to group them
}When to Combine State
✅ Combine State When Values Are Related
// ✅ Good - position coordinates belong together
function DraggableBox() {
const [position, setPosition] = React.useState({ x: 0, y: 0 })
const move = (dx, dy) => {
setPosition(prev => ({
x: prev.x + dx,
y: prev.y + dy
}))
}
// x and y are always used together
// Makes sense as one object
}
// ❌ Bad - splitting related values
function DraggableBox() {
const [x, setX] = React.useState(0)
const [y, setY] = React.useState(0)
const move = (dx, dy) => {
setX(prev => prev + dx) // Two separate updates
setY(prev => prev + dy) // Could get out of sync
}
}✅ Combine State for Form Data
// ✅ Good - form fields grouped
function SignupForm() {
const [formData, setFormData] = React.useState({
username: '',
email: '',
password: '',
confirmPassword: ''
})
const handleChange = (e) => {
const { name, value } = e.target
setFormData(prev => ({
...prev,
[name]: value
}))
}
// All form fields updated with one handler
// Easy to validate, submit, or reset as a group
}✅ Combine State That Updates Together
// ✅ Good - these always update together
function DataFetcher() {
const [apiState, setApiState] = React.useState({
data: null,
isLoading: false,
error: null
})
const fetchData = async () => {
setApiState({ data: null, isLoading: true, error: null })
try {
const response = await fetch('/api/data')
const data = await response.json()
setApiState({ data, isLoading: false, error: null })
} catch (error) {
setApiState({ data: null, isLoading: false, error })
}
}
// These three values represent one logical state
// Only one can be "active" at a time
}State Organization Decision Framework
Ask These Questions:
- Do the values always update together?
Yes → Combine into object
No → Keep separate - Are the values related conceptually?
Yes (e.g., x/y coordinates, firstName/lastName) → Combine
No (e.g., theme and user) → Keep separate - Do you often pass them together?
Yes → Combine into object
No → Keep separate - Is it a form with multiple fields?
Yes → Usually combine
No → Depends on other factors
General Rule of Thumb:
When in doubt, start with separate state variables. It's easier to combine them later if needed than to split a large object. Prefer simplicity and clarity over premature optimization.
Common State Organization Patterns
Pattern 1: Form State
function ContactForm() {
// ✅ Combine form fields
const [formData, setFormData] = React.useState({
name: '',
email: '',
message: ''
})
// ✅ Keep validation separate
const [errors, setErrors] = React.useState({})
// ✅ Keep submission state separate
const [isSubmitting, setIsSubmitting] = React.useState(false)
const handleChange = (e) => {
setFormData(prev => ({
...prev,
[e.target.name]: e.target.value
}))
}
const handleSubmit = async (e) => {
e.preventDefault()
setIsSubmitting(true)
// Submit logic...
setIsSubmitting(false)
}
}Pattern 2: Loading/Error State
function UserProfile() {
// ✅ Combine related loading state
const [loadingState, setLoadingState] = React.useState({
isLoading: false,
error: null
})
// ✅ Keep actual data separate
const [user, setUser] = React.useState(null)
const fetchUser = async () => {
setLoadingState({ isLoading: true, error: null })
try {
const response = await fetch('/api/user')
const data = await response.json()
setUser(data)
setLoadingState({ isLoading: false, error: null })
} catch (error) {
setLoadingState({ isLoading: false, error: error.message })
}
}
}Pattern 3: List with Filters
function ProductList() {
// ✅ Keep source data separate
const [products, setProducts] = React.useState([])
// ✅ Group filter settings
const [filters, setFilters] = React.useState({
category: 'all',
priceRange: [0, 1000],
inStockOnly: false,
sortBy: 'name'
})
// ✅ Keep search separate (updates frequently)
const [searchQuery, setSearchQuery] = React.useState('')
const updateFilter = (key, value) => {
setFilters(prev => ({ ...prev, [key]: value }))
}
}Pattern 4: Wizard/Multi-Step Form
function SignupWizard() {
// ✅ Keep step tracking separate
const [currentStep, setCurrentStep] = React.useState(1)
// ✅ Combine all form data
const [formData, setFormData] = React.useState({
// Step 1
email: '',
password: '',
// Step 2
firstName: '',
lastName: '',
// Step 3
address: '',
city: '',
zipCode: ''
})
const nextStep = () => setCurrentStep(prev => prev + 1)
const prevStep = () => setCurrentStep(prev => prev - 1)
const updateFormData = (updates) => {
setFormData(prev => ({ ...prev, ...updates }))
}
}Updating Nested State
When you have nested objects, updating them requires careful spreading:
function UserSettings() {
const [settings, setSettings] = React.useState({
profile: {
name: 'Alice',
avatar: '/avatar.jpg'
},
preferences: {
theme: 'light',
notifications: true
}
})
// ❌ Wrong - only shallow copy
const updateName = (newName) => {
setSettings({
...settings,
profile: { name: newName } // This erases avatar!
})
}
// ✅ Correct - deep copy
const updateName = (newName) => {
setSettings({
...settings,
profile: {
...settings.profile,
name: newName
}
})
}
// ✅ Better - helper function
const updateProfile = (updates) => {
setSettings(prev => ({
...prev,
profile: {
...prev.profile,
...updates
}
}))
}
updateProfile({ name: 'Bob' }) // Easier!
}Deep Nesting Warning:
If you have deeply nested state (3+ levels), consider:
1. Flattening your state structure
2. Using a state management library (Redux, Zustand)
3. Breaking into smaller components
Deep nesting makes updates error-prone and hard to read!
Interactive Demo
💡 Tip: Edit the code above and click "Run" to see your changes
State Organization Anti-Patterns
❌ Anti-Pattern 1: Duplicating State
// ❌ Bad - count and doubleCount are redundant
function Counter() {
const [count, setCount] = React.useState(0)
const [doubleCount, setDoubleCount] = React.useState(0)
const increment = () => {
setCount(count + 1)
setDoubleCount((count + 1) * 2) // Easy to forget!
}
}
// ✅ Good - compute from existing state
function Counter() {
const [count, setCount] = React.useState(0)
const doubleCount = count * 2 // Calculated, not stored
const increment = () => {
setCount(count + 1)
}
}❌ Anti-Pattern 2: Mirroring Props in State
// ❌ Bad - copying props to state
function UserDisplay({ user }) {
const [userName, setUserName] = React.useState(user.name)
// userName gets out of sync if user prop changes!
}
// ✅ Good - use props directly
function UserDisplay({ user }) {
return <div>{user.name}</div>
}
// ✅ Or if you need to modify it locally
function UserDisplay({ user }) {
const [editedName, setEditedName] = React.useState(user.name)
// Make it clear this is a local edit, not the source
}❌ Anti-Pattern 3: Too Much in One Object
// ❌ Bad - everything in one giant object
function App() {
const [state, setState] = React.useState({
user: null,
theme: 'light',
sidebar: false,
modal: false,
notifications: [],
cart: [],
filters: {},
searchQuery: '',
// ... 20 more properties
})
// Updating is complex and error-prone
const toggleSidebar = () => {
setState({ ...state, sidebar: !state.sidebar })
}
}
// ✅ Good - split into logical groups
function App() {
const [user, setUser] = React.useState(null)
const [theme, setTheme] = React.useState('light')
const [uiState, setUiState] = React.useState({
sidebar: false,
modal: false
})
const [notifications, setNotifications] = React.useState([])
const [cart, setCart] = React.useState([])
const toggleSidebar = () => {
setUiState(prev => ({ ...prev, sidebar: !prev.sidebar }))
}
}Key Takeaways
- You can use multiple
useStatecalls in one component - Split state when values are independent and update separately
- Combine state when values are related and update together
- Form data usually works well as a single object
- Keep loading/error state separate from actual data
- Use the spread operator to update nested state immutably
- Avoid duplicating state - compute derived values instead
- Don't mirror props in state unless you have a good reason
- Start with separate state variables, combine if needed
- Keep state flat - avoid deep nesting when possible
- Group related state for easier updates and passing to children
- State organization affects component maintainability
What's Next?
You now know how to organize state effectively! You understand when to split state into multiple variables and when to combine it into objects. Your components are cleaner and more maintainable.
But there are still some important best practices and patterns to learn. In the next lesson, you'll learn about state best practices:
- Immutability and why it matters
- Where to place state in your component tree
- Avoiding common state mistakes
- Advanced state patterns and techniques
- When to lift state up or down
These best practices will help you write professional, production-ready React code! 🚀