Fixing Slow React Component Re-renders with Memoization

Fixing Slow React Component Re-renders with Memoization

Yuki MartinBy Yuki Martin
How-To & Fixesreactjavascriptwebdevperformancefrontend

What causes unnecessary re-renders in React?

You're building a complex dashboard or a real-time data feed, and suddenly the interface feels sluggish. Even though you aren't changing the actual data, the components keep re-rendering. This usually happens because React's reconciliation process triggers a re-render whenever a parent component's state changes—even if the child component's props haven't actually changed in value. If you're passing objects, arrays, or functions as props, React sees a new reference every time the parent renders, which signals to the child that it needs to update. This is a silent performance killer in large-scale applications.

To fix this, you need to understand how to stabilize references. We'll look at how to use useMemo and useCallback to ensure your components only update when they truly have to.

The Problem with Reference Equality

In JavaScript, {} !== {} and [] !== []. This is a fundamental concept that trips up many developers. When you define a function or an object inside a functional component, it gets recreated on every single render cycle. If that object is passed down to a child component, the child sees a "new" prop and triggers its own render cycle. This cascades through your component tree.

How do I stop a child component from re-rendering?

The first tool in your kit is React.memo. It's a higher-order component that wraps your functional component. It does a shallow comparison of the props. If the props are the same as the last render, React skips the render for that component entirely. However, React.memo alone isn't a silver bullet. If you pass a raw object or a function from the parent, the shallow comparison will always fail because the reference changed.

Let's look at a practical example of how to fix a broken implementation:

Problematic CodeOptimized Code
const data = { id: 1 };
const data = useMemo(() => ({ id: 1 }), []);
const handleClick = () => { ... };
const handleClick = useCallback(() => { ... }, []);

By using useMemo, you tell React to keep the same memory address for that object unless the dependencies change. By using useCallback, you ensure the function identity stays constant across renders.

When should I actually use useMemo and useCallback?

I've seen too many developers wrap every single variable in a memoization hook. This is a mistake. Memoization isn't free—it has a cost in terms of memory and the overhead of dependency tracking. If you're just calculating a simple sum of two numbers, don't bother with useMemo. The cost of the hook is higher than the cost of the calculation.

You should reach for these tools when:

  • You're passing props to a React.memo wrapped component: If the child is memoized, the parent must also stabilize its props, or the memoization is useless.
  • The calculation is expensive: If you're filtering a massive array or performing heavy computations (like parsing a large JSON string), useMemo is a lifesaver.
  • The object is a dependency in a useEffect: If an object is used in a dependency array, it must be stable, or the effect will run on every render.

For more in-depth technical details on how React handles the virtual DOM, check out the