100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
React

React Performance Optimization

Learn how to prevent unnecessary re-renders in React using React.memo, useMemo, and useCallback.

Performance & TestingIntermediate12 min readJul 8, 2026
Analogies

Introduction

React is fast by default because of the virtual DOM, but as applications grow, unnecessary re-renders can slow things down. Performance optimization in React mainly revolves around avoiding wasted work: skipping re-renders of components whose output hasn't changed, and avoiding expensive recalculations on every render. The three main tools for this are React.memo (to memoize components), useMemo (to memoize computed values), and useCallback (to memoize function references).

🏏

Cricket analogy: A team is fast by default with a solid top order, but repeatedly re-reviewing every routine single on DRS wastes time; smart captains skip reviewing unchanged decisions (React.memo), pre-calculate the required run rate once per over (useMemo), and reuse the same fixed field signal (useCallback).

Syntax

jsx
import React, { useMemo, useCallback } from 'react';

// Memoize a component so it only re-renders when its props change
const ExpensiveList = React.memo(function ExpensiveList({ items, onSelect }) {
  console.log('Rendering ExpensiveList');
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id} onClick={() => onSelect(item.id)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
});

function Parent({ items }) {
  // Memoize an expensive derived value
  const sortedItems = useMemo(
    () => [...items].sort((a, b) => a.name.localeCompare(b.name)),
    [items]
  );

  // Memoize a callback so it keeps the same reference across renders
  const handleSelect = useCallback((id) => {
    console.log('Selected item', id);
  }, []);

  return <ExpensiveList items={sortedItems} onSelect={handleSelect} />;
}

Explanation

React.memo wraps a component and performs a shallow comparison of its props between renders; if none of the props changed, React skips re-rendering that component and reuses the last rendered output. useMemo caches the result of a computation between renders and only recalculates it when one of its dependencies changes, which is useful for expensive operations like sorting, filtering, or heavy math. useCallback is similar but caches a function reference instead of a value — this matters because in JavaScript a new function is created on every render, and passing a 'new' function as a prop to a React.memo'd child would defeat the memoization since the prop reference always changes. Together, useMemo and useCallback let memoized children actually skip re-rendering.

🏏

Cricket analogy: React.memo is the coach checking if a fielder's assigned position actually changed before repositioning them; useMemo caches the pre-calculated required run rate instead of recomputing it every ball; useCallback keeps the same fixed hand-signal reference so the memoized fielder doesn't reposition just because a "new" signal object was flashed each ball.

Do not wrap every component in React.memo or every value in useMemo/useCallback by default. Memoization itself has a cost (extra memory and comparison work), and for cheap components or calculations it can make performance worse. Profile first with the React DevTools Profiler, then optimize the components that actually re-render expensively and frequently.

Example

jsx
function App() {
  const [count, setCount] = useState(0);
  const [items] = useState([
    { id: 1, name: 'Banana' },
    { id: 2, name: 'Apple' },
  ]);

  return (
    <div>
      <button onClick={() => setCount((c) => c + 1)}>
        Clicked {count} times
      </button>
      {/* Without React.memo, ExpensiveList would re-render on every click */}
      <Parent items={items} />
    </div>
  );
}

Output

Clicking the button increments count and re-renders App and Parent, but because items never changes reference and handleSelect is memoized with useCallback, ExpensiveList (wrapped in React.memo) does not re-render — the 'Rendering ExpensiveList' log only appears once, on the initial mount, instead of on every click.

🏏

Cricket analogy: Clicking the scoreboard's manual run-adjust button re-renders the whole control panel, but since the fixed fielding chart's reference never changes and the signal callback is memoized, the "repositioning fielders" log only fires once at the start, not on every adjustment.

Key Takeaways

  • React.memo skips re-rendering a component when its props are shallowly equal to the previous render.
  • useMemo caches an expensive computed value and recalculates it only when its dependencies change.
  • useCallback caches a function reference so it doesn't change identity on every render.
  • Memoization only helps when paired correctly: memoized children need memoized callback/object props to actually skip re-renders.
  • Always measure with the React DevTools Profiler before adding memoization — premature optimization can add unnecessary complexity.

Practice what you learned

Was this page helpful?

Topics covered

#React#ReactJsStudyNotes#WebDevelopment#ReactPerformanceOptimization#Performance#Optimization#Syntax#Explanation#StudyNotes#SkillVeris