Learnitweb

React.memo

What is React.memo?

React.memo is a higher-order component (HOC) that optimizes functional components by preventing unnecessary re-renders.

const MemoizedComponent = memo(SomeComponent, arePropsEqual?)

Why Use React.memo?

By default, when a parent component re-renders, all its child components re-render, even if their props haven’t changed.

React.memo prevents these unnecessary re-renders by memoizing the component and only re-rendering it when its props change.

How to Use React.memo?

Without React.memo (Unoptimized)

In this example, ChildComponent re-renders every time ParentComponent updates, even if the name prop hasn’t changed.

import React, { useState } from "react";

const ChildComponent = ({ name }: { name: string }) => {
  console.log("🔄 ChildComponent Rendered");
  return <h2>Hello, {name}!</h2>;
};

const ParentComponent: React.FC = () => {
  console.log("🔄 ParentComponent Rendered");
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      {/* ChildComponent re-renders even when name doesn't change! */}
      <ChildComponent name="John" />
    </div>
  );
};

export default ParentComponent;

Console Output (Without React.memo)

ParentComponent Rendered
ChildComponent Rendered
ParentComponent Rendered
ChildComponent Rendered  // Unnecessary re-render!

Even though name="John" hasn’t changed, ChildComponent still re-renders!

Optimized: Using React.memo

Now, we wrap ChildComponent in React.memo, so it only re-renders when its props change.

import React, { useState, memo } from "react";

const ChildComponent = memo(({ name }: { name: string }) => {
  console.log("🔄 ChildComponent Rendered");
  return <h2>Hello, {name}!</h2>;
});

const ParentComponent: React.FC = () => {
  console.log("🔄 ParentComponent Rendered");
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      {/* ChildComponent will NOT re-render unless 'name' changes */}
      <ChildComponent name="John" />
    </div>
  );
};

export default ParentComponent;

Console Output (With React.memo)

ParentComponent Rendered
ChildComponent Rendered
ParentComponent Rendered

Now, ChildComponent doesn’t re-render when count updates!

How React.memo Works

  1. Memoization: React.memo remembers the last rendered output.
  2. Shallow Prop Comparison: It only re-renders if the props have changed (using a shallow equality check).
  3. Prevents Unnecessary Renders: If the props remain unchanged, React skips re-rendering the component.

When to Use React.memo?

Use React.memo when:

  • The component receives the same props frequently.
  • The component is expensive to render (e.g., complex calculations, large UI updates).
  • The component doesn’t rely heavily on internal state or context for its rendered output.

Don’t use React.memo when:

  • The component relies on hooks like useState, useEffect, or context (useContext) that might change independently of its props.
  • The component has frequently changing props (e.g., dynamic data from an API).
  • The re-rendering cost is low, making memoization unnecessary.

Advanced: Custom Comparison Function

By default, React.memo performs a shallow comparison of props. If you need custom comparison logic, pass a comparison function:

const ChildComponent = memo(
  ({ name, age }: { name: string; age: number }) => {
    console.log("🔄 ChildComponent Rendered");
    return <h2>{name}, Age: {age}</h2>;
  },
  (prevProps, nextProps) => {
    // Only re-render if 'name' changes; ignore changes in 'age'
    return prevProps.name === nextProps.name;
  }
);

Now, ChildComponent only re-renders if name changes, ignoring age.

Common Mistakes with React.memo

Using React.memo effectively requires an understanding of its limitations. Here are some common mistakes:

  1. Overusing React.memo:
    • Mistake: Wrapping every functional component with React.memo without evaluating whether the component is expensive to re-render.
    • Consequence: This can add unnecessary complexity and might even introduce performance overhead in scenarios where re-rendering cost is minimal.
  2. Shallow Comparison Pitfalls:
    • Mistake: Assuming that React.memo will catch changes in deeply nested objects or arrays.
    • Consequence: Since React.memo uses a shallow comparison by default, changes in nested data structures might not trigger a re-render. Use custom comparison functions or memoize the data with useMemo before passing it as props.
  3. Ignoring Functions as Props:
    • Mistake: Passing inline functions or objects as props without memoization.
    • Consequence: Every render creates a new function or object reference, which causes the component to re-render even if the actual values have not changed. To avoid this, wrap functions with useCallback or objects with useMemo.
  4. Relying on React.memo for Components with Internal State or Context:
    • Mistake: Believing that React.memo can optimize a component that frequently updates its own state or consumes context values that change over time.
    • Consequence: Since React.memo only checks props, any changes in internal state or context will still trigger a re-render regardless of memoization.
    • Explanation of “It won’t work if the component depends on state or context”:
      React.memo only prevents re-renders based on prop changes. If your component relies on its own internal state (using useState) or values provided via React Context (useContext), those changes are not controlled by props. This means that even if the props remain unchanged, a change in state or context will force the component to re-render. In such cases, the benefits of React.memo may be limited or nullified because the component’s output depends on more than just its props.
  5. Misunderstanding When Re-renders Occur:
    • Mistake: Expecting that React.memo will completely eliminate all re-renders of a component.
    • Consequence: Developers might be surprised when the component still re-renders because of parent state updates, context changes, or local state updates that React.memo does not control.