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.
function useLatest<T>(value: T): { readonly current: T };
The same object comes back every render; only .current changes.
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...
.current holds the newest value. After a render with value X, .current is X — not the previous value, and not a copy of it.0, '', false, null, and undefined all have to land in .current unchanged. Don't skip the write for them.