DEV Community

Darren Hwang
Darren Hwang

Posted on

Preventing unnecessary re-renders using React.memo, useMemo and useCallback hooks

Preventing unnecessary re-renders in React is crucial for maintaining performance, especially in complex component trees. Here is a comprehensive summary of the key concepts, focusing on React.memo and related hooks.

The Problem with Re-renders

When a parent component re-renders, its children automatically re-render as well. In large-scale applications, this default behavior can create noticeable performance bottlenecks.

There are four primary reasons why a component will re-render itself, along with one common misconception.

1. State Changes

This is the "root" source of all re-renders. When a component’s state is updated (typically via useState or useReducer), the component triggers a re-render to reflect the new data.

  • Trigger: Usually happens inside event callbacks or useEffect hooks.

2. Parent Re-renders

If a parent component re-renders, React will automatically re-render all of its children.

  • Direction: The render cycle always flows "down" the tree. A child re-rendering does not naturally trigger a parent re-render unless it invokes a state change in that parent.

3. Context Changes

When the value provided by a Context.Provider changes, every component consuming that context will re-render.

  • Note: This occurs even if the component only utilizes a portion of the data that didn't change. These re-renders cannot be prevented with simple memoization.

4. Hook Changes

Since everything inside a custom hook belongs to the component utilizing it, standard rules for state and context apply:

  • A state change inside a hook triggers a re-render of the "host" component.
  • If a hook consumes a Context that changes, the "host" component re-renders.

⛔ The "Props Change" Myth

A prevalent misconception is that a component re-renders because its props changed. In reality, for props to change, the parent must re-render first.

  • If a component is not memoized, it will re-render when its parent does, regardless of whether the props are identical or entirely different.
  • Props only become the deciding factor for a re-render when using memoization techniques like React.memo.

Component Re-render Triggers

Trigger Description
State The component's internal useState or useReducer was updated.
Parent The component's parent is re-rendering.
Context A useContext value the component subscribes to has changed.
Hooks A state or context change occurred inside a hook used by the component.

Using React.memo

React.memo is a higher-order component that memoizes a child component. It intercepts the default behavior and prevents re-renders if the component's incoming props have not changed.

The Pitfall: Referential Equality

React.memo relies on shallow comparison. Objects can cause unintended re-renders because of how React compares props—specifically through referential equality.

  • Referential Equality: For primitive values (strings, numbers, booleans), React compares the actual value. However, for objects, arrays, and functions, it compares the memory reference.
  • New References on Render: Every time a parent component re-renders, any prop defined inline (e.g., user={{ id: 1 }}) is recreated as a brand-new object in memory.
  • The Effect: Because this new object has a different reference than the one from the previous render, React assumes the prop has changed. Even if the child is wrapped in React.memo, it will ignore the memoization and re-render because the shallow comparison returns false.

The Solution: useMemo and useCallback

To prevent this broken memoization, you must preserve the object or function's reference across re-renders.

  • Use the useMemo hook to cache objects and arrays.
  • Use the useCallback hook to cache functions.

Custom Comparison

Similar to the legacy shouldComponentUpdate lifecycle method in class components, you can pass a custom comparison function as the second argument to React.memo. This allows you to define the exact logic for when a component should update.

Best Practices

Optimization should always be a "last resort." Prioritize solid component composition techniques first (like moving state down or passing elements as children). Composition is generally more predictable and maintainable than adding complex layers of memoization hooks.

Based on the React re-renders guide.

Top comments (0)