useLayoutEffectCompare
Description
useLayoutEffectCompare is a wrapper around a layout‑effect that runs the effect only when dependencies have “meaningful” changes. It supports three comparison modes:
- a dependencies array with shallow index‑by‑index comparison (
===), - a custom comparator function plus a separate value,
- comparator‑only, when comparison logic is fully encapsulated inside the comparator.
The effect runs synchronously during the layout phase (before paint), so it’s suitable for DOM measurements and synchronous pre‑paint preparation.
Signature
ts
// 1) Dependencies array
function useLayoutEffectCompare(
effect: () => void | (() => void),
deps: unknown[],
): void;
// 2) Custom comparator + value
function useLayoutEffectCompare<ComparedValue>(
effect: () => void | (() => void),
compare: (prev: ComparedValue, next: ComparedValue) => boolean,
comparedValue: ComparedValue,
): void;
// 3) Comparator only
function useLayoutEffectCompare(
effect: () => void | (() => void),
compare: () => boolean,
): void;Parameters
effect— the layout‑effect function; may return a cleanup function.deps— a dependencies array; compared shallowly by index (===).compare— a comparator function that must returntruewhen values are equal (no change) andfalsewhen they differ (there is a change).comparedValue— the value to be compared by the custom comparator.
Returns
void— same as regular effects.
Examples
1) Dynamic dependencies for synchronous measurements
tsx
import { useLayoutEffectCompare } from '@webeach/react-hooks';
function Measure({ nodes }: { nodes: HTMLElement[] }) {
useLayoutEffectCompare(() => {
// Runs only on a real change of `nodes` (values/order/length)
const bounds = nodes.map((el) => el.getBoundingClientRect());
syncLayout(bounds);
}, nodes);
return null;
}2) Custom comparator by the “meaning” of size
tsx
import { useLayoutEffectCompare } from '@webeach/react-hooks';
type Size = { width: number; height: number } | null;
function ResizeConsumer({ size }: { size: Size }) {
useLayoutEffectCompare(
() => {
applySize(size);
},
(prev, next) =>
prev?.width === next?.width && prev?.height === next?.height,
size,
);
return null;
}3) Comparator only (logic inside a closure)
tsx
import { useLayoutEffectCompare } from '@webeach/react-hooks';
function GridRuler() {
useLayoutEffectCompare(
() => {
drawRulers();
return () => eraseRulers();
},
() => {
return layoutCache.version === lastVersionRef.current;
},
);
return null;
}Behavior
Effect trigger
- The effect runs only on a detected change of dependencies. For an array, shallow index‑by‑index comparison is used; for a comparator, it must return
falseon change.
- The effect runs only on a detected change of dependencies. For an array, shallow index‑by‑index comparison is used; for a comparator, it must return
Dynamic dependencies array
- In the
depsform, the array can be fully dynamic: its length and order may change between renders. Comparison accounts for both values by index and length; a length change is also considered a change. Unlike the standarduseLayoutEffect, there is no requirement to keep the array length strictly stable across renders.
- In the
Layout semantics
- The effect runs synchronously before paint, which is convenient for DOM reads/writes and layout synchronization. Use responsibly: heavy work will delay paint.
Cleanup
- The cleanup function returned from
effectruns before the next effect execution and on unmount.
- The cleanup function returned from
Flexible call forms
- Choose between a dependencies array, a custom comparator with a separate value, or a comparator‑only form — whichever fits the task.
When to Use
- Synchronous DOM measurements before paint: size calculations, positioning, scrolling.
- When you care about semantic changes of dependencies, not just reference changes.
- Complex dependency objects that require a comparator by “meaning”.
When Not to Use
- If a regular
useEffect(after paint) is sufficient and synchronicity isn’t required. - If heavy computations in a layout‑effect would block rendering — move them after paint or optimize.
Common Mistakes
Inverted comparator logic
- The comparator must return
truefor equality andfalsefor difference. Otherwise, the effect will re‑run unnecessarily or be skipped.
- The comparator must return
Rebuilding the deps array every render
- If you construct a fresh array with new references each render, remember that changed order/length also counts as a change — the effect will re‑run.
Overly heavy layout‑effect
- Layout‑effects run before paint; avoid long‑running operations inside them.