usePreviousDistinct(value, compare?) returns the last value that was actually different from the current one — not the value from the previous render. Renders where the value stayed the same are skipped, so the answer survives however many times the component rerenders in between.
That gap is the whole question. usePrevious hands back the previous render's value, which is the same thing right up until the component rerenders for a reason that has nothing to do with your value: a parent rerendered, an unrelated piece of state moved, a notification count ticked. One idle rerender later, "what was it before?" answers "the same thing it is now", and the change you were tracking is gone.
function usePreviousDistinct<T>(
value: T,
compare?: (previous: T | undefined, next: T) => boolean,
): T | undefined;
compare returns true when the two values count as equal — that is, when nothing changed. It defaults to Object.is.
You are animating a tab switch and need to know which panel to slide out:
function Tabs({ tab }) {
const previousTab = usePreviousDistinct(tab);
return <Panel from={previousTab} to={tab} />;
}
// tab: inbox inbox archive archive spam
// render: 1 2 3 4 5
// returns: undefined undefined inbox inbox archive
Renders 2 and 4 change nothing, so the answer does not move. usePrevious would return inbox, inbox, archive, archive for renders 2–5 — on renders 2 and 4 that is just the tab you are already on.
A comparator decides what "different" means:
// Two objects with the same id are the same tab, so this is not a change:
const previousTab = usePreviousDistinct(tab, (a, b) => a.id === b.id);
// tab: {id: 1} {id: 1} {id: 2}
// returns: undefined undefined {id: 1}
usePrevious. Getting it right is the question.compare(previous, next) returns true when they are EQUAL. Not "did it change" — the polarity is backwards from what most people guess, and it matches the react-use hook this mirrors.Object.is. So objects and arrays are compared by reference: a rebuilt object with identical contents is a change.0, '', false, null and undefined are all values to remember, not stand-ins for "nothing".