![]()
A common performance pitfall in React is unintentionally passing new object references as props, which can trigger unnecessary re-renders even when the actual data hasn’t meaningfully changed.
The Problem
When you pass objects, arrays, or functions as props, React does a shallow comparison. If the reference changes, the component will re-render:
// This will cause child to re-render every time Parent renders
function Parent() {
return <Child style={{ color: 'blue' }} />;
}
Solutions
1. Memoize Object Props with useMemo
function Parent() {
const style = useMemo(() => ({ color: 'blue' }), []);
return <Child style={style} />;
}
2. Memoize Array Props
function Parent() {
const items = useMemo(() => ['a', 'b', 'c'], []);
return <Child items={items} />;
}
3. Stabilize Function Props with useCallback
function Parent() {
const handleClick = useCallback(() => {
console.log('Clicked');
}, []);
return <Child onClick={handleClick} />;
}
4. Move Static Objects Outside the Component
const staticStyle = { color: 'blue' };
function Parent() {
return <Child style={staticStyle} />;
}
5. For Context Providers, Memoize the Value
function App() {
const [user, setUser] = useState(null);
const contextValue = useMemo(() => ({
user,
login: (userData) => setUser(userData),
logout: () => setUser(null)
}), [user]);
return (
<AuthContext.Provider value={contextValue}>
{/* children */}
</AuthContext.Provider>
);
}
6. Use Custom Comparison for Deep Equality
For class components, implement shouldComponentUpdate with deep comparison:
class Child extends React.Component {
shouldComponentUpdate(nextProps) {
return !deepEqual(this.props, nextProps);
}
// ...
}
Or for function components, use React.memo with a custom comparison function:
const Child = React.memo(
function Child(props) { /* ... */ },
(prevProps, nextProps) => {
return prevProps.objectProp.id === nextProps.objectProp.id;
}
);
7. Flatten Your Props
Instead of passing big objects, pass individual primitives:
// Instead of:
<Child user={user} />
// Do:
<Child userId={user.id} userName={user.name} />
8. Consider Using Immutable Data Structures
Libraries like Immer can help manage state without creating new references unnecessarily:
import produce from 'immer';
function reducer(state, action) {
return produce(state, draft => {
switch (action.type) {
case 'UPDATE_NAME':
draft.user.name = action.payload;
break;
}
});
}
9. Use React’s Built-in Components for Elements
For style objects specifically, consider using the built-in style prop which React may optimize:
// This might be more efficient than passing an object
<div style={{ color: 'blue' }} />
Debugging Tips
- Use React DevTools to highlight re-renders
- Add a render counter:
const renderCount = useRef(0);
useEffect(() => {
renderCount.current++;
console.log(`Rendered ${renderCount.current} times`);
});
- Check prop references:
useEffect(() => {
console.log('Object prop reference changed');
}, [props.objectProp]);
By carefully managing object references in your props, you can significantly reduce unnecessary re-renders and improve your application’s performance.
