Build a custom hook that gives a component access to a value from its last render. React always re-renders with the current props and state — but sometimes you need to compare "now" against "a moment ago": did this prop actually change, which direction is a number heading, what was selected before the user picked something new. usePrevious(value) returns whatever value was on the previous render, and undefined on the very first render, when there is no previous yet.
function usePrevious<T>(value: T): T | undefined;
The returned value lags exactly one render behind the input. It is undefined only on the first render.
function Price({ amount }) {
const previous = usePrevious(amount);
const direction =
previous === undefined ? 'first' : amount > previous ? 'up' : 'down';
return <span data-trend={direction}>{amount}</span>;
}
// render 1: amount=100 → previous=undefined → 'first'
// render 2: amount=120 → previous=100 → 'up'
// render 3: amount=90 → previous=120 → 'down'
// The hook lags one render behind the value it is given:
// value: 1 2 3 4
// returns: undefined 1 2 3
undefined. There is no earlier render to remember, so the very first call yields undefined.X, the next render's usePrevious returns X — never two renders back.0, '', or false, the hook must return that, not undefined. "No previous value" and "a previous value that happens to be falsy" are different.You'll keep a value that deliberately trails one render behind the current one, by storing it in a ref that you only update after each render commits.
React hands a component the current props and state on every render and then forgets the past. But plenty of UI logic is about change, not the present value: highlight a number green when it rose and red when it fell, run an animation only when a selection actually switched, log "field went from empty to filled." All of these need yesterday's value standing next to today's. usePrevious is that memory — give it the current value and it returns what you handed it last time.
The trick rests on two facts about React. First, a ref (useRef) is a mutable box whose .current survives across renders without triggering a re-render when you change it. Second, an effect (useEffect) runs after the render has been painted, not during it. Put those together: during render you read the ref — which still holds the value from last time — and return it. Then, after the render commits, an effect writes the current value into the ref, arming it for next time. The read always happens before the write, one render apart, so what you read is always one render old.
The instinct is to stash the value in a ref and hand it back:
const { useRef } = require('react');
function usePrevious(value) {
const ref = useRef(value);
ref.current = value; // update during render
return ref.current;
}
This compiles and looks reasonable, but it never returns the previous value — it returns the current one. The write ref.current = value runs during render, before the return, so by the time you read ref.current it already holds the new value. You've overwritten the memory before reading it. The fix isn't a different storage box; it's a different moment to write.
const { useRef, useEffect } = require('react');
function usePrevious(value) {
// A ref is a box that persists across renders and, crucially, does NOT
// trigger a re-render when we mutate it. It starts undefined — there is no
// previous value before the first render.
const ref = useRef(undefined);
// The effect runs AFTER this render commits. So during render the return
// below still sees the value from the PREVIOUS render; only afterwards do we
// overwrite the box with the current value, readying it for next time.
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
module.exports = { usePrevious };
The whole solution turns on when the write happens. By moving ref.current = value into a useEffect, the assignment is deferred until after the render has returned. The return ref.current therefore reads the box before this render's value lands in it — so it yields the value from one render ago. On the first render the effect hasn't run yet and the ref is still undefined, which is exactly the right answer for "there is no previous value."
Track a value that goes 1, then 2, then 3 across three renders:
1. useRef(undefined) creates the box holding undefined. The function returns ref.current, which is undefined — correct, there's no previous value yet. After the render commits, the effect runs and sets ref.current = 1.2. The box still holds 1 from the last effect. The function returns 1 — the previous value. After commit, the effect sees the value changed and sets ref.current = 2.3. The box holds 2. The function returns 2. After commit, the effect sets ref.current = 3, ready for a fourth render.At every step the returned value trails the input by exactly one render, because the read (during render) always precedes the write (after commit).
ref.current = value; return ref.current overwrites the memory before you read it, so you always get the current value back. Fix: defer the write into a useEffect, which runs after the render reads the ref.value. useRef(value) makes the first render return the current value instead of undefined. Fix: start the ref at undefined so "no previous value yet" is represented honestly.ref.current || undefined (or if (ref.current)) throws away a legitimate previous value of 0, '', or false. Fix: store and return the value as-is; only the genuine first render should be undefined.usePrevious with a custom comparison. Pass an equality function so the "previous" only updates when the value meaningfully changed (e.g. deep-equal for objects), letting callers ignore no-op renders.useState would trigger an extra re-render every time it updated. A ref avoids that: it remembers across renders without scheduling one, which is exactly what a passive "memory" should do.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.
Build a custom hook that gives a component access to a value from its last render. React always re-renders with the current props and state — but sometimes you need to compare "now" against "a moment ago": did this prop actually change, which direction is a number heading, what was selected before the user picked something new. usePrevious(value) returns whatever value was on the previous render, and undefined on the very first render, when there is no previous yet.
function usePrevious<T>(value: T): T | undefined;
The returned value lags exactly one render behind the input. It is undefined only on the first render.
function Price({ amount }) {
const previous = usePrevious(amount);
const direction =
previous === undefined ? 'first' : amount > previous ? 'up' : 'down';
return <span data-trend={direction}>{amount}</span>;
}
// render 1: amount=100 → previous=undefined → 'first'
// render 2: amount=120 → previous=100 → 'up'
// render 3: amount=90 → previous=120 → 'down'
// The hook lags one render behind the value it is given:
// value: 1 2 3 4
// returns: undefined 1 2 3
undefined. There is no earlier render to remember, so the very first call yields undefined.X, the next render's usePrevious returns X — never two renders back.0, '', or false, the hook must return that, not undefined. "No previous value" and "a previous value that happens to be falsy" are different.You'll keep a value that deliberately trails one render behind the current one, by storing it in a ref that you only update after each render commits.
React hands a component the current props and state on every render and then forgets the past. But plenty of UI logic is about change, not the present value: highlight a number green when it rose and red when it fell, run an animation only when a selection actually switched, log "field went from empty to filled." All of these need yesterday's value standing next to today's. usePrevious is that memory — give it the current value and it returns what you handed it last time.
The trick rests on two facts about React. First, a ref (useRef) is a mutable box whose .current survives across renders without triggering a re-render when you change it. Second, an effect (useEffect) runs after the render has been painted, not during it. Put those together: during render you read the ref — which still holds the value from last time — and return it. Then, after the render commits, an effect writes the current value into the ref, arming it for next time. The read always happens before the write, one render apart, so what you read is always one render old.
The instinct is to stash the value in a ref and hand it back:
const { useRef } = require('react');
function usePrevious(value) {
const ref = useRef(value);
ref.current = value; // update during render
return ref.current;
}
This compiles and looks reasonable, but it never returns the previous value — it returns the current one. The write ref.current = value runs during render, before the return, so by the time you read ref.current it already holds the new value. You've overwritten the memory before reading it. The fix isn't a different storage box; it's a different moment to write.
const { useRef, useEffect } = require('react');
function usePrevious(value) {
// A ref is a box that persists across renders and, crucially, does NOT
// trigger a re-render when we mutate it. It starts undefined — there is no
// previous value before the first render.
const ref = useRef(undefined);
// The effect runs AFTER this render commits. So during render the return
// below still sees the value from the PREVIOUS render; only afterwards do we
// overwrite the box with the current value, readying it for next time.
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
module.exports = { usePrevious };
The whole solution turns on when the write happens. By moving ref.current = value into a useEffect, the assignment is deferred until after the render has returned. The return ref.current therefore reads the box before this render's value lands in it — so it yields the value from one render ago. On the first render the effect hasn't run yet and the ref is still undefined, which is exactly the right answer for "there is no previous value."
Track a value that goes 1, then 2, then 3 across three renders:
1. useRef(undefined) creates the box holding undefined. The function returns ref.current, which is undefined — correct, there's no previous value yet. After the render commits, the effect runs and sets ref.current = 1.2. The box still holds 1 from the last effect. The function returns 1 — the previous value. After commit, the effect sees the value changed and sets ref.current = 2.3. The box holds 2. The function returns 2. After commit, the effect sets ref.current = 3, ready for a fourth render.At every step the returned value trails the input by exactly one render, because the read (during render) always precedes the write (after commit).
ref.current = value; return ref.current overwrites the memory before you read it, so you always get the current value back. Fix: defer the write into a useEffect, which runs after the render reads the ref.value. useRef(value) makes the first render return the current value instead of undefined. Fix: start the ref at undefined so "no previous value yet" is represented honestly.ref.current || undefined (or if (ref.current)) throws away a legitimate previous value of 0, '', or false. Fix: store and return the value as-is; only the genuine first render should be undefined.usePrevious with a custom comparison. Pass an equality function so the "previous" only updates when the value meaningfully changed (e.g. deep-equal for objects), letting callers ignore no-op renders.useState would trigger an extra re-render every time it updated. A ref avoids that: it remembers across renders without scheduling one, which is exactly what a passive "memory" should do.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.