If your Redux-connected React components are mysteriously rerendering like they’ve had five cups of coffee ☕ — chances are you're making one of these common but sneaky mistakes.
Let’s explore what not to do, why it causes performance issues, and the sharpest ways to fix it.
❌ 1. Destructuring Redux Objects Inside Components
const { name, email } = useSelector((state) => state.user); // ❌
Looks clean, but this subscribes to the entire user
object. So if any field inside user
changes — even user.avatarUrl
— your component rerenders.
🧠 Redux selectors use reference equality (
===
). Destructuring causes you to compare new objects every render.
✅ Instead, pick only the fields you need:
const name = useSelector((state) => state.user.name); // ✅
const email = useSelector((state) => state.user.email);
❌ 2. Selecting the Whole Object, Then Accessing Inside
const user = useSelector((state) => state.user); // ❌
const name = user.name;
Even though you're only using name
, you've subscribed to the whole user
object. Any change (e.g., password update, login status) triggers a rerender.
✅ Better:
const name = useSelector((state) => state.user.name); // ✅
❌ 3. Pulling Unfiltered Data You Don’t Need
Let’s say you're rendering a widget layout:
const layout = [{ id: 'w1' }, { id: 'w3' }];
Now:
const widgets = useSelector((state) => state.widgets); // ❌ bad idea
You just subscribed to every widget in the store, even if your component only displays two.
✅ Fix: Filter only what’s relevant + use a custom equality function
const widgetsInLayout = useSelector(
(state) => {
const allWidgets = state.widgets;
const ids = layout.map((l) => l.id);
return ids.reduce((acc, id) => {
if (allWidgets[id]) acc[id] = allWidgets[id];
return acc;
}, {});
},
(prev, next) => {
const prevKeys = Object.keys(prev);
const nextKeys = Object.keys(next);
if (prevKeys.length !== nextKeys.length) return false;
return prevKeys.every((key) => {
return (
prev[key]?.config === next[key]?.config &&
prev[key]?.data === next[key]?.data
);
});
}
);
Now your component only rerenders when widgets used in the current layout actually change — not on every widget update.
❌ 4. Returning Objects in Selectors Without Equality Check
const userInfo = useSelector((state) => ({
name: state.user.name,
age: state.user.age,
})); // ❌ causes rerenders
Even if values don’t change, this object is new on every render — causing unnecessary updates.
✅ Fix: Use shallow equality
import { shallowEqual, useSelector } from 'react-redux';
const userInfo = useSelector(
(state) => ({
name: state.user.name,
age: state.user.age,
}),
shallowEqual // ✅ avoids rerenders if values don’t change
);
❌ 5. Creating Selectors Inside Components
const selectUserName = (state) => state.user.name;
const MyComponent = () => {
const name = useSelector(selectUserName); // ❌ Looks okay, but can re-run unnecessarily
}
If the selector isn’t memoized or reused, it could lead to recomputation or unstable results across renders.
✅ TL;DR Cheat Sheet
❌ Don’t | ✅ Do |
---|---|
Destructure full Redux slices | Pick exact fields in useSelector
|
Return whole objects in selectors | Return only required keys |
Skip equality checks | Use shallowEqual or custom compare |
Create selectors inside components | Define them outside |
Pull entire state trees | Filter context-specific slices only |
🧠 Pro Tip: Always Think in Terms of React's Rerender Triggers
React only rerenders when props or state change. If you're feeding it fresh objects or large slices from Redux, you're making it think something has changed — even when it hasn't.
Keep your selectors pure, focused, and stable. Use tools like shallowEqual
and custom equality checks only when needed, and never fetch more than you actually use.
With a little precision, your Redux-connected components will stay fast, predictable, and frustration-free. 🚀
📝 Disclaimer:
This post was assisted by AI (ChatGPT) for clarity and structure.
Top comments (0)