In React, when working with object state, you need to be careful about reference equality. Here’s how to properly update object state without unnecessarily creating new object references:
The Problem
When you do this with class components:
this.setState({
user: {
...this.state.user,
name: 'New Name'
}
});
Or with function components:
setUser(prev => ({
...prev,
name: 'New Name'
}));
You’re creating a new object reference each time, which can cause unnecessary re-renders if other parts of your state haven’t changed.
Optimization Techniques
1. Class Components: Functional setState
this.setState(prevState => {
// Only create new object if name actually changed
if (prevState.user.name === 'New Name') return null;
return {
user: {
...prevState.user,
name: 'New Name'
}
};
});
2. Function Components: useMemo/useCallback
const [user, setUser] = useState({ name: 'John', age: 30 });
const updateName = useCallback((newName) => {
setUser(prev => {
if (prev.name === newName) return prev;
return { ...prev, name: newName };
});
}, []);
3. Using Immer for Complex State
import produce from 'immer';
// Class component
this.setState(produce(draft => {
draft.user.name = 'New Name';
}));
// Function component
setUser(produce(draft => {
draft.name = 'New Name';
}));
4. Selective Updates
// Only update specific properties
this.setState({
'user.name': 'New Name'
}, () => {
// Optional callback
});
When to Worry About References
Only optimize when:
- Your object is very large
- You’re passing the object to many child components
- You’re using the object in dependency arrays
- Performance issues are actually observed
Best Practices
- Don’t prematurely optimize – React is already efficient at reconciliation
- Use Immer for complex nested state – It handles references optimally
- Consider breaking up state – Maybe you don’t need one large object
- Use multiple state variables in function components instead of one big object
// Instead of:
const [user, setUser] = useState({ name: '', age: 0 });
// Consider:
const [name, setName] = useState('');
const [age, setAge] = useState(0);
Remember that in most cases, the spread operator pattern is perfectly fine and readable. Only optimize object references when you have measurable performance issues.