DEV Community

Davide Cannerozzi
Davide Cannerozzi

Posted on

Understanding React's useTransition Hook: Keep Your UI Smooth

The Problem

Imagine you're building a search feature for a list of 2,000 products. Every time a user types a letter, your app filters the entire list and re-renders it. What happens? The input lags. The user's typing feels sluggish and frustrating.

This is a common problem when dealing with heavy updates in React. What if we could tell React: "Hey, updating this list is important, but keeping the input smooth is MORE important"?

That's exactly what useTransition does.

What is useTransition?

useTransition is a React hook (introduced in React 18) that lets you mark certain updates as non-urgent. This means React can prioritize user interactions (like typing) over heavy rendering tasks (like filtering a huge list).

Basic Syntax

const [isPending, startTransition] = useTransition();
Enter fullscreen mode Exit fullscreen mode
  • startTransition: A function that wraps non-urgent updates
  • isPending: A boolean that tells you if the transition is still in progress (useful for showing loading states)

How It Works

React categorizes updates into two types:

  1. Urgent updates: User interactions like typing, clicking, hovering
  2. Non-urgent updates: Heavy operations like filtering, sorting, complex calculations

When you wrap an update in startTransition, you're telling React: "This can wait. Keep the UI responsive first."

Real Example: Filtering a Large List

Let's build a practical example. We have 2,000 products and want to filter them as the user types.

The Data

// hugeList.ts
export interface Product {
  id: number;
  name: string;
}

export const hugeList: readonly Product[] = Array.from(
  { length: 2000 },
  (_, i) => ({
    id: i,
    name: `Product ${i}`,
  })
);
Enter fullscreen mode Exit fullscreen mode

Without useTransition (Laggy)

function App() {
  const [value, setValue] = useState("");
  const [filteredProducts, setFilteredProducts] = useState(hugeList);

  const handleChange = (newValue) => {
    setValue(newValue);
    // This blocks the input!
    setFilteredProducts(
      hugeList.filter((item) =>
        item.name.toLowerCase().includes(newValue.toLowerCase())
      )
    );
  };

  return (
    <>
      <input value={value} onChange={(e) => handleChange(e.target.value)} />
      <ProductList items={filteredProducts} />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Problem: Every keystroke triggers a heavy filter operation that blocks the input.

With useTransition (Smooth)

function App() {
  const [value, setValue] = useState("");
  const [filteredProducts, setFilteredProducts] = useState([...hugeList]);
  const [isPending, startTransition] = useTransition();

  useEffect(() => {
    startTransition(() => {
      setFilteredProducts(
        hugeList.filter((item) =>
          item.name.toLowerCase().includes(value.toLowerCase())
        )
      );
    });
  }, [value]);

  return (
    <>
      <input value={value} onChange={(e) => setValue(e.target.value)} />
      {isPending && <p>Filtering...</p>}
      <ProductList items={filteredProducts} />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Result: The input stays smooth! React keeps it responsive while filtering happens in the background.

When to Use useTransition

Perfect for:

  • Filtering or sorting large lists (1,000+ items)
  • Heavy calculations that slow down the UI
  • Complex rendering (charts, graphs, data tables)
  • Tab navigation with heavy content

When NOT to Use useTransition

Skip it for:

  • Small, fast updates (simple toggles, counters)
  • Updates that must happen immediately
  • API calls (better handled with loading states and async patterns)
  • Animations (use CSS or animation libraries)

Important Limits

useTransition is not magic. It doesn't solve everything:

  • 50,000+ DOM elements will still be slow. You need virtualization (like react-window)
  • It's not a replacement for React.memo, lazy loading, or code splitting
  • It only helps with React updates, not external operations

Best Practices

  1. Use only for genuinely heavy updates - Don't wrap everything in startTransition
  2. Combine with other optimizations - Use memo, useMemo, and proper component structure
  3. Show feedback with isPending - Let users know something is happening
  4. Test with realistic data - 10 items won't show the difference, 2,000 will

Conclusion

useTransition is a powerful tool for building smooth, responsive UIs in React. It's not about making things faster—it's about making the right things fast (user interactions) and letting the heavy things wait (complex rendering).

💻 Complete Code

Check out the full working project on my GitHub:

github.com/DavideCannerozzi/react-use-transition-search


Top comments (0)