React Performance Optimization
React Performance Optimization : Deep Dive into React.memo, useMemo, useCallback & Lazy Loading
When building scalable React apps, performance becomes critical.
React gives us powerful tools to:
Prevent unnecessary re-renders
Avoid expensive recalculations
Reduce bundle size
Letβs break them down deeply π
π§ 1. React.memo β Component Memoization
π What is it?
React.memo is a Higher Order Component (HOC) that prevents re-rendering of a component if props havenβt changed.
π How React Normally Works
Whenever a parent re-renders: π All child components re-render by default
Even if props are the same π¬
β Solution
const Child = React.memo(({ name }) => {
console.log("Child Rendered");
return <h1>{name}</h1>;
});
βοΈ Under the Hood
Performs shallow comparison of props
If props unchanged β skips render
β οΈ Important Gotcha
<Child user={{ name: "Paras" }} />
β This will re-render every time Why? β New object reference on every render
β Fix
const user = useMemo(() => ({ name: "Paras" }), []);
<Child user={user} />
π₯ When to Use
β Pure functional components β Expensive UI rendering β Large lists / tables
β When NOT to Use
β Small/simple components β Props change frequently
π§ 2. useMemo β Value Memoization
π What is it?
Caches the result of a computed value and recalculates only when dependencies change.
π§ Syntax
const memoizedValue = useMemo(() => computeFn(), [deps]);
π₯ Example
const sortedList = useMemo(() => {
console.log("Sorting...");
return items.sort((a, b) => a.price - b.price);
}, [items]);
β οΈ Without useMemo
Sorting runs on every render
Bad for large datasets
π‘ Real Use Cases
Sorting / filtering lists
Expensive calculations
Derived state
β οΈ Common Mistake
const value = useMemo(() => num * 2, []);
β Missing dependency β stale value bug
π§ Rule
π Always include all dependencies
π§ 3. useCallback β Function Memoization
π What is it?
Returns a memoized function (same reference unless dependencies change)
π§ Syntax
const memoizedFn = useCallback(() => {}, [deps]);
π₯ Problem Without It
const handleClick = () => {};
π New function created every render π Causes child re-renders
β
With useCallback
const handleClick = useCallback(() => {
console.log("Clicked");
}, []);
π Real Use Case (Important)
const Child = React.memo(({ onClick }) => {
console.log("Child rendered");
return <button onClick={onClick}>Click</button>;
});
const Parent = () => {
const handleClick = useCallback(() => {
console.log("Clicked");
}, []);
return <Child onClick={handleClick} />;
};
β Child will NOT re-render unnecessarily
β οΈ Important Insight
π useCallback is mainly useful with:
React.memoDependency arrays
π useMemo vs useCallback
| Feature | useMemo | useCallback |
|---|---|---|
| Returns | Value | Function |
| Use case | Expensive computation | Stable function reference |
| Internally | Runs function | Returns function |
π 4. Lazy Loading & Code Splitting
π Problem
React apps bundle everything into one file β large bundle size β slow initial load
β Solution: Code Splitting
Load components only when needed
πΉ React.lazy
const Dashboard = React.lazy(() => import("./Dashboard"));
πΉ Suspense
<Suspense fallback={<h2>Loading...</h2>}>
<Dashboard />
</Suspense>
π§ What Happens?
Component is NOT loaded initially
When needed β dynamically imported
Fallback UI shown while loading
π₯ Best Use Cases
Route-based splitting (React Router)
Heavy components (charts, dashboards)
Admin panels
β οΈ Limitation
β Works only with default exports
β Fix
const Dashboard = React.lazy(() =>
import("./Dashboard").then(module => ({ default: module.Dashboard }))
);
β‘ Combining Everything (Real-World Pattern)
const List = React.memo(({ items, onSelect }) => {
console.log("List rendered");
return items.map(item => (
<div key={item.id} onClick={() => onSelect(item)}>
{item.name}
</div>
));
});
function App({ data }) {
const sortedData = useMemo(() => {
return data.sort((a, b) => a.name.localeCompare(b.name));
}, [data]);
const handleSelect = useCallback((item) => {
console.log(item);
}, []);
return <List items={sortedData} onSelect={handleSelect} />;
}
β οΈ Over-Optimization Trap (VERY IMPORTANT)
Most beginners do this:
useMemo(() => value, []);
useCallback(() => {}, []);
React.memo(Component);
β Everywhere.
π§ Reality:
These hooks also have a cost:
Memory overhead
Comparison cost
β Rule of Thumb
π Use only when:
Re-render is actually expensive
You see performance issues
Profiling shows bottlenecks
π§ͺ How to Decide? (Pro Tip)
Use React DevTools Profiler
π Check:
Which component re-renders
How often
How long it takes
π§ Final Mental Model
| Tool | Think Like |
|---|---|
| React.memo | "Skip re-render if same props" |
| useMemo | "Cache computed value" |
| useCallback | "Cache function reference" |
| React.lazy | "Load component later" |
π Conclusion
React performance optimization is not about using all tools everywhere β itβs about using the right tool at the right place.
First make it work. Then make it fast. π