<strong>useTransition</strong>
Hook in React<strong>useTransition</strong>
?Imagine you’re building a search feature for a large dataset. As the user types, the UI updates with search results. However, if rendering the results is slow, the UI may feel laggy. How can we ensure a smooth, responsive experience?
This is where React’s useTransition
hook helps. It allows us to prioritize urgent updates (like typing) while deferring expensive updates (like rendering a long list). This keeps the app feeling snappy and interactive.
Let’s say you have an app where users filter a list of products as they type. Without useTransition
, every keystroke immediately triggers a re-render, which can slow down typing. Using useTransition
, we can ensure that:
<strong>useTransition</strong>
<strong>useTransition</strong>
Work?useTransition
lets us mark updates as non-blocking, meaning React won’t prioritize them over urgent interactions (like typing or clicking).
It returns two values:
jsx
CopyEdit
const [isPending, startTransition] = useTransition();
<strong>isPending</strong>
: Boolean that tells us if a transition is ongoing. Useful for showing a loading state.<strong>startTransition</strong>
: A function that wraps low-priority updates.<strong>useTransition</strong>
Without useTransition
, every keystroke triggers a blocking update, making typing slow when filtering a large dataset.
import React, { useState } from "react";
const products = Array.from({ length: 10000 }, (_, i) => `Product ${i + 1}`);
function ProductList() {
const [query, setQuery] = useState("");
const [filteredProducts, setFilteredProducts] = useState(products);
const handleSearch = (e) => {
setQuery(e.target.value);
// Expensive operation: filtering 10,000 items every keystroke
setFilteredProducts(products.filter((p) => p.includes(e.target.value)));
};
return (
<div>
<input type="text" value={query} onChange={handleSearch} placeholder="Search..." />
<ul>
{filteredProducts.map((product) => (
<li key={product}>{product}</li>
))}
</ul>
</div>
);
}
export default ProductList;
filteredProducts
.<strong>useTransition</strong>
for a Smoother UI<strong>startTransition</strong>
import React, { useState, useTransition } from "react";
const products = Array.from({ length: 10000 }, (_, i) => `Product ${i + 1}`);
function ProductList() {
const [query, setQuery] = useState("");
const [filteredProducts, setFilteredProducts] = useState(products);
const [isPending, startTransition] = useTransition();
const handleSearch = (e) => {
setQuery(e.target.value);
// Move expensive filtering inside startTransition
startTransition(() => {
setFilteredProducts(products.filter((p) => p.includes(e.target.value)));
});
};
return (
<div>
<input type="text" value={query} onChange={handleSearch} placeholder="Search..." />
{isPending && <p>Loading...</p>} {/* Show a loading state */}
<ul>
{filteredProducts.map((product) => (
<li key={product}>{product}</li>
))}
</ul>
</div>
);
}
export default ProductList;
<strong>isPending</strong>
) improves UX.<strong>useTransition</strong>
?✅ Use useTransition
when:
❌ Avoid useTransition
if:
useTransition
prioritizes urgent updates while deferring expensive ones.Let's explore how can we further optimise user experience when dealing with complex, long computations, using the useTransition hook.
With useTransition we can make React apps more responsive ...
... since users expect smooth experiences, especially when handling large datasets or complex UI updates.
Without optimization, React updates can cause the UI to lag, especially during filtering or sorting operations.
The useTransition hook helps defer non-urgent updates while keeping urgent interactions instant. Let's take a look at an example.
When updating state inside an event handler, React blocks the UI until the update completes.
Imagine filtering a list of 10,000 products. Without optimization, each keystroke triggers a re-render.
The culprit is this filtering methods that has to filter thousands of records on each keystroke, freezing the UI until the filtering is complete.
Consider this example that uses over 5000000 records to filter in your app.
In our query handler, we filter the list with the current value of the filter query, leading to a very sluggish experience. Try typing your values in the search box. Let's fix that!
First, let's import the useTransition hook
Then, let's initiate the hook, obtaining the isPending flag and startTransition function
Then, we use the startTransition function providing a parameter which is a function in which we filter our long list.
Last, we can use the isLoading flag to show user that the system is filtering the records.
Typing in the query field will now yield in a much better user experience! Let's break it down.
With useTransition, we can prioritize fast updates while deferring slow ones.
React processes updates asynchronously, keeping interactions responsive.
useTransition gives us two values: isPending (indicates loading) and startTransition (wraps low-priority updates).
Any state updates inside startTransition are considered non-urgent and executed asynchonously.
isPending is a boolean value that tells us when a transition is happening—useful for showing loading indicators.
So, when should you use useTransition?
The Best Use Cases are: Filtering large lists, Sorting heavy datasets, or Rendering complex components
Do not use this hook when updates must be immediate, for example in form validation. Also avoid the use when the expensive update is already optimized, for example using memoization.