useLatestLoading saved progress…

useLatest

useLatest(value) returns a ref object whose .current always holds the value from the most recent render. The ref object itself never changes — the same box comes back on every render — so any code that grabs it once can keep reading fresh values out of it.

That matters because of stale closures. A callback created inside an effect that runs once ([] dependencies) captures the props and state of the render that created it, and keeps reading those forever, no matter how many renders follow. useLatest gives that callback a box to read instead of a value to remember.

Signature

function useLatest<T>(value: T): { readonly current: T };

The same object comes back every render; only .current changes.

Examples

A mount-only interval captures the first render's count and never lets go:

function Ticker({ count }) {
  useEffect(() => {
    const id = setInterval(() => console.log(count), 1000);
    return () => clearInterval(id);
  }, []); // set up once — this closure keeps render 1's count forever
}
// count goes 0, 1, 2, 3... but the interval logs 0, 0, 0, 0...

The same interval, reading the box instead:

function Ticker({ count }) {
  const latestCount = useLatest(count);

  useEffect(() => {
    const id = setInterval(() => console.log(latestCount.current), 1000);
    return () => clearInterval(id);
  }, []); // still set up once — the ref is stable, so nothing needs to re-run
}
// count goes 0, 1, 2, 3... and the interval logs 0, 1, 2, 3...

Notes

  • Return the same ref object every render. Callers hold onto it inside long-lived callbacks; handing back a new object each render leaves them reading the old one.
  • .current holds the newest value. After a render with value X, .current is X — not the previous value, and not a copy of it.
  • Falsy values are values. 0, '', false, null, and undefined all have to land in .current unchanged. Don't skip the write for them.
  • Any type is fair game. Numbers, strings, objects, functions — store the reference you were handed.
  • Nothing to clean up. The ref owns no subscription and no timer; it dies with the component.
Loading editor…