Well, sort of...
I’m talking about useDeferredValue
hook, and it’s almost does what you think it does.
It doesn’t debounce in the generalized way like, for example, Lodash.debounce where a piece of logic is delayed until a certain time has passed
since its last been called but rather it delays the rendering of the UI with the deferred value applied.
When you think about it for a second, in the context of React being a tool to render UI this makes a lot of sense!
Here’s an example of useDeferredValue
in action:
function SearchPage() {
const [query, setQuery] = React.useState('');
const deferredQuery = React.useDeferredValue(query);
// ...
}
It might not be obvious yet how useDeferredValue
works so here’s the explainer from the docs:
“During the initial render, the returned deferred value will be the same as the value you provided.
During updates, React will first attempt a re-render with the old value (so it will return the old value),
and then try another re-render in background with the new value (so it will return the updated value).”
Difference from throttle and debounce?
There are two common optimization techniques you might have used before in this scenario:
- Debouncing means you’d wait for the user to stop typing (e.g. for a second) before updating the list.
- Throttling means you’d update the list every once in a while (e.g. at most once a second). While these techniques are helpful in some cases, useDeferredValue is better suited to optimizing rendering because it is deeply integrated with React itself and adapts to the user’s device.
Unlike debouncing or throttling, it doesn’t require choosing any fixed delay. If the user’s device is fast (e.g. powerful laptop), the deferred re-render would happen almost immediately and wouldn’t be noticeable. If the user’s device is slow, the list would “lag behind” the input proportionally to how slow the device is.
Also, unlike with debouncing or throttling, deferred re-renders done by useDeferredValue are interruptible by default. This means that if React is in the middle of re-rendering a large list, but the user makes another keystroke, React will abandon that re-render, handle the keystroke, and then start rendering in background again. By contrast, debouncing and throttling still produce a janky experience because they’re blocking: they merely postpone the moment when rendering blocks the keystroke.
If the work you’re optimizing doesn’t happen during rendering, debouncing and throttling are still useful. For example, they can let you fire fewer network requests. You can also use these techniques together.
For more info and examples (including using useDeferredValue
with React Suspense) check out https://react.dev/reference/react/useDeferredValue