React: useTransition() vs useDeferredValue()

Before you read this article, be sure to check out the official React 18 release blog post.

That’s because React 18 introduced an important new concept: concurrency.

Concurrency means that multiple state updates are performed at the same time. Specifically, certain state updates take precedence over others to make the overall user interface responsive.

useTransition() and useDeferredValue()
Here is a very brief summary:

useTransition() in action
The useTransition() function 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).

A call to useTransition() returns an array containing exactly two elements: a boolean value isPending that indicates whether a low-priority status update is pending, and a startTransition() function that can be wrapped around the status update to tell React that it is a low-priority update.

This is how the useTransition() hook can 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>
);
}

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 reacts 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 at the end generated or changed because of the state update.

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() wrapping 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.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store