Skip to main content

memo

Memoize a functional component so that it only re-renders when its props actually change. This was previously known as React.pure.

Signature

function memo<P = {}>(
  component: FunctionalComponent<P>,
  comparer?: (prev: P, next: P) => boolean
): FunctionComponent<P>
component
FunctionalComponent<P>
required
The functional component to memoize.
comparer
(prev: P, next: P) => boolean
Optional custom comparison function. Should return true if the props are equal (skip render), or false if they are different (re-render).If not provided, a shallow comparison is performed automatically.
returns
FunctionComponent<P>
A memoized version of the component that only re-renders when props change.

Usage

Basic Memoization

Memoize a component with automatic shallow prop comparison:
import { memo } from 'preact/compat';

const ExpensiveComponent = memo(({ value, label }) => {
  console.log('Rendering ExpensiveComponent');
  
  return (
    <div>
      <strong>{label}:</strong> {value}
    </div>
  );
});

// This component will only re-render when value or label changes

Custom Comparison

Provide a custom comparison function for complex props:
import { memo } from 'preact/compat';

const UserCard = memo(
  ({ user }) => (
    <div>
      <h3>{user.name}</h3>
      <p>{user.email}</p>
    </div>
  ),
  (prevProps, nextProps) => {
    // Return true if props are equal (skip render)
    return prevProps.user.id === nextProps.user.id &&
           prevProps.user.name === nextProps.user.name &&
           prevProps.user.email === nextProps.user.email;
  }
);

Array Props

Compare array contents:
import { memo } from 'preact/compat';

const List = memo(
  ({ items }) => (
    <ul>
      {items.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
  ),
  (prevProps, nextProps) => {
    // Deep comparison of array contents
    if (prevProps.items.length !== nextProps.items.length) {
      return false;
    }
    
    return prevProps.items.every((item, index) => 
      item.id === nextProps.items[index].id
    );
  }
);

With Hooks

Memo works seamlessly with hooks:
import { memo, useState, useCallback } from 'preact/compat';

const Counter = memo(({ onIncrement }) => {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Local +1</button>
      <button onClick={onIncrement}>Parent +1</button>
    </div>
  );
});

function Parent() {
  const [value, setValue] = useState(0);
  
  // Memoize callback to prevent Counter from re-rendering
  const increment = useCallback(() => {
    setValue(v => v + 1);
  }, []);
  
  return <Counter onIncrement={increment} />;
}

Implementation Details

The memo implementation in Preact:
export function memo(c, comparer) {
  function shouldUpdate(nextProps) {
    let ref = this.props.ref;
    let updateRef = ref == nextProps.ref;
    if (!updateRef && ref) {
      ref.call ? ref(null) : (ref.current = null);
    }

    if (!comparer) {
      return shallowDiffers(this.props, nextProps);
    }

    return !comparer(this.props, nextProps) || !updateRef;
  }

  function Memoed(props) {
    this.shouldComponentUpdate = shouldUpdate;
    return createElement(c, props);
  }
  
  Memoed.displayName = 'Memo(' + (c.displayName || c.name) + ')';
  Memoed.prototype.isReactComponent = true;
  Memoed.type = c;
  return Memoed;
}

Key Features

  1. Shallow Comparison: By default, uses shallowDiffers to compare props
  2. Custom Comparison: Accepts optional custom comparison function
  3. Ref Handling: Properly handles ref changes
  4. Display Name: Automatically generates display name for debugging

Comparison Logic

Default Shallow Comparison

When no comparer is provided, memo performs a shallow comparison:
function shallowDiffers(a, b) {
  for (let i in a) if (i !== '__source' && !(i in b)) return true;
  for (let i in b) if (i !== '__source' && a[i] !== b[i]) return true;
  return false;
}
This checks if:
  • Any keys exist in one object but not the other
  • Any values differ using strict equality (!==)

Custom Comparer Return Value

Important: The comparer function should return:
  • true if props are equal (skip re-render)
  • false if props are different (re-render)
This is the opposite of shouldComponentUpdate!

Performance Considerations

When to Use memo

Good use cases:
  • Components that render frequently with the same props
  • Components with expensive render calculations
  • List items in a large list
  • Components receiving callback props from parent
Avoid memo when:
  • Props change on every render anyway
  • Component is already fast
  • The memo overhead outweighs render cost

Measuring Performance

import { memo } from 'preact/compat';

const SlowComponent = ({ data }) => {
  console.time('SlowComponent render');
  
  // Expensive computation
  const result = expensiveCalculation(data);
  
  console.timeEnd('SlowComponent render');
  
  return <div>{result}</div>;
};

// Compare with and without memo
const MemoizedSlow = memo(SlowComponent);

Type Definitions

type MemoExoticComponent<C extends FunctionalComponent<any>> =
  FunctionComponent<ComponentProps<C>> & {
    readonly type: C;
  };

export function memo<P = {}>(
  component: FunctionalComponent<P>,
  comparer?: (prev: P, next: P) => boolean
): FunctionComponent<P>;

export function memo<C extends FunctionalComponent<any>>(
  component: C,
  comparer?: (
    prev: ComponentProps<C>,
    next: ComponentProps<C>
  ) => boolean
): C;

Checking if a Component is Memoized

import { isMemo } from 'preact/compat';

const MyComponent = memo(() => <div>Hello</div>);

console.log(isMemo(MyComponent)); // true

Best Practices

  1. Profile First: Use browser dev tools to identify slow components before memoizing
  2. Memoize Callbacks: Combine with useCallback for callback props
  3. Memoize Values: Use useMemo for computed values passed as props
  4. Custom Comparers: Only use custom comparers when necessary - they add overhead
  5. Ref Stability: Ensure ref callbacks are stable to avoid unnecessary updates

Common Patterns

Memoizing with Context

import { memo, useContext } from 'preact/compat';

const ThemedButton = memo(({ label }) => {
  const theme = useContext(ThemeContext);
  return <button className={theme}>{label}</button>;
});

Memoizing List Items

import { memo } from 'preact/compat';

const ListItem = memo(({ item, onDelete }) => (
  <li>
    {item.name}
    <button onClick={() => onDelete(item.id)}>Delete</button>
  </li>
));

function List({ items }) {
  const handleDelete = useCallback((id) => {
    // delete logic
  }, []);
  
  return (
    <ul>
      {items.map(item => (
        <ListItem key={item.id} item={item} onDelete={handleDelete} />
      ))}
    </ul>
  );
}

Source

Implementation: compat/src/memo.js:1-35

Build docs developers (and LLMs) love