Most React developers know about React.memo()
, but treating it as a silver bullet for performance issues is a common mistake I see in production code. While React.memo()
can be useful, it's just one tool in what should be a comprehensive optimization strategy.
React.memo
is a higher-order component that prevents unnecessary re-renders when props haven't changed:
const MemoizedComponent = React.memo(MyComponent);
Its limitations:
Performs shallow comparison of props by default
Doesn't help with components that use context
Won't prevent re-renders when a parent component re-renders
Adds overhead for components that rarely receive the same props
1. Component Structure Matters
Often, the most effective optimization is restructuring components. By extracting the changing parts into separate components, we prevent expensive re-renders when unrelated state changes:
// Before: ExpensiveList re-renders when count changes
function ParentComponent() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
<ExpensiveList items={items} />
</div>
);
}
// After: Isolated state changes
function ParentComponent() {
const [count, setCount] = useState(0);
return (
<div>
<Counter count={count} setCount={setCount} />
<ExpensiveList items={items} />
</div>
);
}
2. State Placement
Place state as close as possible to where it's used. When state is placed high in the component tree, all child components receive new props on state updates, triggering unnecessary re-renders:
// Instead of this at app level
function App() {
const [userSettings, setUserSettings] = useState({});
// Many nested components later...
return <DeepNestedTree userSettings={userSettings} />;
}
// Place state closer to usage
function UserSettingsSection() {
const [userSettings, setUserSettings] = useState({});
return <SettingsPanel settings={userSettings} />;
}
3. Effective Use of useCallback
When passing functions as props, avoid recreating them on every render. Functions are objects in JavaScript, and creating new function instances causes child components to see "different" props, triggering unnecessary re-renders:
// Problem - new function created on every render
function MyComponent({ onSubmit }) {
const [formData, setFormData] = useState({});
const handleSubmit = () => {
onSubmit(formData);
};
return <ExpensiveChild onSubmit={handleSubmit} />;
}
// Solution - memoize the callback
function MyComponent({ onSubmit }) {
const [formData, setFormData] = useState({});
const handleSubmit = useCallback(() => {
onSubmit(formData);
}, [onSubmit, formData]);
return <ExpensiveChild onSubmit={handleSubmit} />;
}
4. Split Context by Update Frequency
Context triggers re-renders for all consumers when its value changes. By splitting contexts based on how frequently their values change, you reduce the number of components that re-render when any part of your application state updates:
// Instead of one large context
const AppContext = createContext({
user: {}, // Changes rarely
theme: {}, // Changes occasionally
notifications: [] // Changes frequently
});
// Split by update frequency
const UserContext = createContext();
const ThemeContext = createContext();
const NotificationContext = createContext();
5. Windowing for Long Lists
For long lists, implement virtualization to render only visible items. This prevents rendering thousands of DOM elements at once, which blocks the main thread and consumes excessive memory:
import { FixedSizeList } from 'react-window';
function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
<ListItem data={items[index]} />
</div>
);
return (
<FixedSizeList
height={500}
width="100%"
itemCount={items.length}
itemSize={50}
>
{Row}
</FixedSizeList>
);
}
After applying structural optimizations, selectively use React.memo
for:
Pure components that render frequently
Components that receive the same props often
Expensive components deep in the tree
Use custom comparison for complex props:
const MemoizedComponent = React.memo(MyComponent, (prev, next) => {
return prev.item.id === next.item.id;
});
Effective React optimization is about strategy, not just applying memo
everywhere. Start with component structure and state placement, then measure and apply targeted optimization where it matters most. These are my observations; I’d love to hear more ways, views, or techniques you’ve found effective!
What performance optimization techniques have you found most effective in your React applications?
Join Mehul on Peerlist!
Join amazing folks like Mehul and thousands of other people in tech.
Create ProfileJoin with Mehul’s personal invite link.
2
21
2