Skip to main content

Command Palette

Search for a command to run...

React Performance Optimization

Updated
β€’5 min read

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.memo

  • Dependency 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?

  1. Component is NOT loaded initially

  2. When needed β†’ dynamically imported

  3. 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. πŸš€