React usePrevious Hook
Tracking the previous value of a prop or piece of state lets you diff renders, animate transitions, and detect specific change patterns. This snippet shows the canonical one-liner usePrevious, a useChanged variant that returns whether the value just changed, and a useHistory hook that keeps the last N values for undo / redo. All three are tiny but easy to get subtly wrong.
204 views
6
function usePrevious(value) {
const ref = useRef(undefined);
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
function useRef(v) { return { current: v }; }
function useEffect(fn) { fn(); }
const prev = usePrevious(42);
console.log('previous on first render:', prev);The hook stores the value in a ref and updates it inside useEffect, which runs AFTER the render commits. That ordering is the whole trick: during render, ref.current still holds the value from the previous commit, so the component sees the prior value while React holds the new one. On the very first render the ref is undefined, which usually models the initial state correctly. This is the textbook implementation and the one most React projects copy-paste.
function useChanged(value) {
const previous = usePrevious(value);
return previous !== value;
}
// Demo: render-by-render, useChanged should be true exactly when value differs.
console.log('changed?', useChanged('a'));
console.log('changed?', useChanged('a'));Often you do not actually need the previous value, just whether it changed. useChanged builds on usePrevious and returns a boolean, which keeps call sites concise (if (useChanged(userId)) { ... }). The first render returns true because previous is undefined, which usually matches the desired behaviour: treat 'first time you see a value' as a change. If you want the first render to count as unchanged, special-case it with a second ref that flips after the first commit.
function useHistory(value, size = 5) {
const ref = useRef([]);
useEffect(() => {
ref.current = [...ref.current, value].slice(-size);
}, [value]);
return ref.current;
}
const history = useHistory('hello', 3);
console.log('history on first render:', history);When you need more than just the last value (an undo stack, a velocity calculation, a streak detector), keeping the last size values in a ring is straightforward. The ref-plus-effect pattern keeps the history off the React tree so updates do not trigger extra renders. The slice(-size) cap keeps memory bounded while preserving insertion order. Because useEffect runs after commit, callers see history that excludes the current value during the first render after a change, which is the intuitive behaviour for diff-style logic.
