useTransition vs useDeferredValue

useTransition vs useDeferredValue
The main difference is what you apply them to.
useTransition
wraps the state update logic. You use it when you're the one triggering the state change and want to mark that update as low-priority.useDeferredValue
wraps a value (like a state variable or prop). You use it to get a delayed version of a value that might be changing rapidly.
Both hooks help keep your app responsive by deferring non-urgent UI updates, but they do it from different angles.
useTransition
useTransition
gives you a way to tell React that a specific state update is not urgent and can be interrupted by more important updates (like user input).
It returns an array with two items:
isPending
: A boolean that tells you if the low-priority update is still in progress.startTransition
: A function that you wrap your low-priority state update in.
When to use it: Use useTransition when you have access to the state-setting function and want to show a pending/loading indicator while the transition is happening.10
Example:
Imagine a search input that filters a large list. Typing should feel instant, but updating the list might be slow.
import { useState, useTransition } from 'react';
function App() {
const [isPending, startTransition] = useTransition();
const [inputValue, setInputValue] = useState('');
const [searchQuery, setSearchQuery] = useState('');
const handleInputChange = (e) => {
// High-priority update: keeps the input field responsive
setInputValue(e.target.value);
// Low-priority update: wrapped in startTransition
startTransition(() => {
setSearchQuery(e.target.value);
});
};
return (
<div>
<input
onChange={handleInputChange}
value={inputValue}
/>
{isPending && <p>Loading list...</p>}
{/* <SlowList query={searchQuery} /> */}
</div>
);
}
In this code, updating the inputValue
is immediate. The slower work of updating searchQuery
(which would trigger the <SlowList>
to re-render) is deferred, and isPending
becomes true until it's done.
useDeferredValue
useDeferredValue
accepts a value and returns a new copy of that value that will "lag behind" during urgent re-renders.
When to use it: This is useful when you don't control the state update logic directly. For instance, if the value is coming from a parent component as a prop or from a third-party hook. It's a more direct way to de-prioritize a part of your UI.
Example:
Let's refactor the previous example using useDeferredValue.
import { useState, useDeferredValue } from 'react';
function App() {
const [searchQuery, setSearchQuery] = useState('');
// The deferredQuery will lag behind the actual searchQuery
const deferredQuery = useDeferredValue(searchQuery);
const handleInputChange = (e) => {
setSearchQuery(e.target.value);
};
// You can manually check if the component is "stale"
const isStale = searchQuery !== deferredQuery;
return (
<div>
<input
onChange={handleInputChange}
value={searchQuery}
/>
{isStale && <p>Loading list...</p>}
{/* The list uses the deferred value, so it won't re-render on every keystroke */}
{/* <SlowList query={deferredQuery} /> */}
</div>
);
}
Here, the input updates the searchQuery
state immediately. React re-renders, but deferredQuery
still holds the previous value. React then schedules a low-priority re-render with the new value for deferredQuery
. This keeps the input responsive.