Preventing Unnecessary Re-renders from State Changes
Excessive component re-renders due to state changes are a common performance bottleneck in React applications. Here’s how to identify and fix these issues:
Common Causes of Unnecessary Re-renders
- State updates that don’t affect UI
- Large state objects where only part changes
- State lifted too high in component hierarchy
- New object/array references triggering updates
- Context values changing too frequently
Optimization Techniques
1. Memoize Components with React.memo
const MyComponent = React.memo(function MyComponent({ data }) {
// Only re-renders when props actually change
return <div>{data.value}</div>;
});
2. Use useMemo for Derived State
function UserList({ users }) {
const activeUsers = useMemo(() => {
return users.filter(user => user.isActive);
}, [users]); // Only recalculates when users change
}
3. Split State Appropriately
// Instead of:
const [state, setState] = useState({ a: 1, b: 2, c: 3 });
// Do:
const [a, setA] = useState(1);
const [b, setB] = useState(2);
const [c, setC] = useState(3);
4. Use useCallback for Stable Function References
const handleClick = useCallback(() => {
// Stable reference unless dependencies change
doSomething(id);
}, [id]);
5. Optimize Context Consumption
// Create selector hook
function useUser() {
const { name } = useContext(UserContext);
return name;
}
// Or use context selector libraries
const name = useContextSelector(UserContext, user => user.name);
6. Use State Management Libraries for Complex State
// With Zustand for example:
const useStore = create(set => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
}));
// Components only re-render when their selected state changes
Advanced Patterns
1. State Colocation (Move state down)
// Move state to the lowest component that needs it
// rather than lifting to the highest common ancestor
2. Batched State Updates
// React 18+ automatically batches:
function handleClick() {
setCount(c => c + 1);
setName('Updated'); // Single re-render
}
3. Transitions for Non-Urgent Updates
import { useTransition } from 'react';
function App() {
const [isPending, startTransition] = useTransition();
function handleChange(value) {
startTransition(() => {
// Non-urgent state update
setSearchQuery(value);
});
}
}
Debugging Re-renders
- React DevTools Profiler
- Record and analyze re-renders
- Highlight updates when components render
- Why Did You Render
npm install @welldone-software/why-did-you-render
import whyDidYouRender from '@welldone-software/why-did-you-render';
whyDidYouRender(React);
- Console Logs
useEffect(() => {
console.log('Component re-rendered');
}); // No dependency array
Best Practices
- Keep state as local as possible
- Use composition to avoid prop drilling
- Consider separating state and dispatch
- For lists, assign stable keys
- Use React.memo for heavy components
- Be mindful of context value changes