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.
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
- Shallow Comparison: By default, uses
shallowDiffers to compare props
- Custom Comparison: Accepts optional custom comparison function
- Ref Handling: Properly handles ref changes
- 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!
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
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
- Profile First: Use browser dev tools to identify slow components before memoizing
- Memoize Callbacks: Combine with
useCallback for callback props
- Memoize Values: Use
useMemo for computed values passed as props
- Custom Comparers: Only use custom comparers when necessary - they add overhead
- 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