React: useTransition() vs useDeferredValue()
React 18 introduced the Concurrency concept and with it two new Hooks: useTransition() and useDeferredValue(). Understanding when to use which can be tricky though.
React 18 & Concurrency
Before diving into this article, you should definitely go through my React 18 update video and the official React 18 release blog post.
Because React 18 introduced a key new concept: Concurrency. And concurrency is all about performing multiple state updates at once. Or, to be precise, it's all about prioritizing certain state updates over others, so that the overall user interface can be kept responsive.
useTransition() and useDeferredValue()
I created a complete video tutorial where I explain when concurrency matters. In that video, I also show how useTransition()
and useDeferredValue()
can be used to give React hints about the importance of state updates and what the difference between these two hooks is. I also provide a small demo app.
Here's the very short summary:
useTransition() in Action
useTransition()
can be used to tell React that certain state updates have a lower priority (i.e., all other state updates or UI rendering triggers have a higher priority).
When calling useTransition()
, you get back an array with exactly two elements: An isPending
boolean value, telling you whether the low-priority state update is still pending, and a startTransition()
function that can be wrapped around a state update to tell React, that it is a low-priority update.
Here's how the useTransition()
hook could be used:
function App() {const [isPending, startTransition] = useTransition();const [filterTerm, setFilterTerm] = useState('');const filteredProducts = filterProducts(filterTerm);function updateFilterHandler(event) {startTransition(() => {setFilterTerm(event.target.value);});}return (<div id="app"><input type="text" onChange={updateFilterHandler} />{isPending && <p>Updating List...</p>}<ProductList products={filteredProducts} /></div>);}
This example is taken from the video mentioned before.
The setFilterTerm()
state updating function is wrapped by startTransition()
and therefore React treats this state updating code with a lower priority. In the demo, this means that the input field stays responsive and react instantly to keystrokes. Without the use of useTransition()
the app can get unresponsive, especially on slower devices.
useDeferredValue() in Action
useTransition()
gives you full control since you decide which code should be wrapped and treated as "low priority". Sometimes though, you might not have access to the actual state updating code (e.g., because it's performed by some third-party library). Or, for some reason, you can't use useTransition()
.
In such cases, you could use useDeferredValue()
instead.
With useDeferredValue()
, you don't wrap the state updating code but instead the value that's in the end generated or changed because of the state update (either the state value itself or some value that's computed based on the state value, as in the demo app shown in the video).
A basic example could look like this:
function ProductList({ products }) {const deferredProducts = useDeferredValue(products);return (<ul>{deferredProducts.map((product) => (<li>{product}</li>))}</ul>);}
Here, useDeferredValue()
is used to wrap products
. As a result, React will perform other state or UI updates with a higher priority than updates related to the products
value.
When Should You Use Which?
As mentioned above, the difference is that useTransition()
wraps the state updating code, whilst useDeferredValue()
wraps a value that's affected by the state update. You don't need to (and shouldn't) use both together, since they achieve the same goal in the end.
Instead, it makes sense to prefer useTransition()
, if you have some state update that should be treated with a lower priority and you have access to the state updating code. If you don't have that access, use useDeferredValue()
.
Don't Overdo It!
Very important: You should not start wrapping all your state updates with useTransition()
or all your values with useDeferredValue()
. Those hooks should be used if you have a complex user interface or component that can't be optimized with other means. You should always keep other performance improvements like lazy loading, pagination or performing work in worker threads or on the backend in mind.