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 (1)

Collapse
 
_ce71f996a40957eaecea profile image
่œป่œ“้˜Ÿ้•ฟ

nice๏ผI am learning react