Introduction

As your React application grows, you may notice it slowing down or lagging, especially when components re-render more often than necessary. React re-renders components whenever their state or props change — but sometimes, unnecessary re-renders can hurt performance. This is where memo, useMemo, and useCallback come in. These tools help you control re-renders, avoid heavy calculations on each render, and prevent functions from being recreated unnecessarily.

Why Do React Apps Become Slow?

React becomes slow when components re-render too many times. This usually happens because:

Using memo, useMemo, and useCallback helps fix these issues and makes your app run more smoothly.

1. React.memo — Prevents Unnecessary Re-renders of Components

React.memo is used to stop a component from re-rendering if its props haven’t changed.

How It Works

React normally re-renders child components whenever the parent renders. But with memo, React compares old and new props — if they are the same, it skips the render.

Example Without memo

function Child({ count }) {
  console.log("Child rendered");
  return <h2>Count: {count}</h2>;
}

function App() {
  const [value, setValue] = useState(0);

  return (
    <>
      <button onClick={() => setValue(value + 1)}>Increase</button>
      <Child count={10} />
    </>
  );
}

Here, Child re-renders every time App re-renders, even though count never changes.

Fix Using memo

const Child = React.memo(function Child({ count }) {
  console.log("Child rendered");
  return <h2>Count: {count}</h2>;
});

Now Child renders only once, unless its props change.

When to Use memo

When Not to Use memo

2. useMemo — Caches Expensive Calculations

useMemo is used when you have a slow or heavy calculation that you do not want to repeat on every render.

How It Works

useMemo remembers the result of a function and recalculates it only when dependencies change.

Example Without useMemo

function App() {
  const [count, setCount] = useState(0);

  const heavyResult = slowCalculation(count);

  return (
    <>
      <h2>Result: {heavyResult}</h2>
      <button onClick={() => setCount(count + 1)}>Increase</button>
    </>
  );
}

The function slowCalculation runs on every render, which slows the app.

Fix Using useMemo

const heavyResult = useMemo(() => slowCalculation(count), [count]);

Now the calculation runs only when count changes.

When to Use useMemo

When Not to Use useMemo

3. useCallback — Prevents Function Recreation

In React, functions are recreated on every render. This causes issues when passing functions as props to child components.

useCallback returns a memoized version of a function, so it is not recreated unless dependencies change.

Example Without useCallback

function App() {
  const [count, setCount] = useState(0);

  const handleClick = () => setCount(count + 1);

  return <Child handleClick={handleClick} />;
}

React recreates handleClick on every render → Child re-renders unnecessarily.

Fix Using useCallback

const handleClick = useCallback(() => {
  setCount(count + 1);
}, [count]);

Now handleClick only changes when count changes.

When to Use useCallback

When Not to Use useCallback

How memo, useMemo, and useCallback Work Together

These three tools work best when combined.

Example

const filteredList = useMemo(() => {
  return list.filter(item => item.active);
}, [list]);

const handleSelect = useCallback((id) => {
  console.log(id);
}, []);

const ListComponent = React.memo(function ListComponent({ data, onSelect }) {
  return data.map(item => (
    <div key={item.id} onClick={() => onSelect(item.id)}>
      {item.name}
    </div>
  ));
});

<ListComponent data={filteredList} onSelect={handleSelect} />;

This combination results in maximum performance improvement.

Best Practices for Performance Optimization

Conclusion

Optimizing React performance becomes easier once you understand how memo, useMemo, and useCallback work. These tools help prevent unnecessary re-renders, avoid expensive recalculations, and stabilize functions that React recreates on each render. By applying these techniques correctly, you can build faster, smoother, and more efficient React applications. With the right balance of optimization and clean code, your app will perform well even as it grows in size and complexity.