← Back to Chapters

Transitions (useTransition)

⚛️ Transitions (useTransition)

⚛️ Introduction

The useTransition() hook, introduced in React 18, lets you mark certain state updates as non-urgent. This enables React’s concurrent rendering engine to keep the UI responsive by prioritizing urgent updates (like typing) over slower, background ones (like filtering large lists).

In simple terms — it helps React know which updates can wait a little.

? Syntax

? View Syntax
// Basic usage of useTransition
const [isPending, startTransition] = useTransition();
  • isPending → Boolean indicating if a transition is still running.
  • startTransition(callback) → Wrap non-urgent updates inside this function.

? Example: Search Filter

❌ Without useTransition (lags)

In this example, the heavy computation blocks the UI, making typing feel sluggish.

? View Code Example
import React, { useState } from "react";

function SearchBad() {
  const [query, setQuery] = useState("");
  const [results, setResults] = useState([]);

  const handleChange = (e) => {
    const value = e.target.value;
    setQuery(value);

    // Simulate heavy computation that freezes UI
    const filtered = Array(5000)
      .fill("Item")
      .map((x, i) => x + i)
      .filter((x) => x.includes(value));

    setResults(filtered);
  };

  return (
    <div className="p-3">
      <input value={query} onChange={handleChange} />
      <p>Results: {results.length}</p>
    </div>
  );
}

✅ With useTransition (smooth)

The input stays responsive because React defers the expensive filtering logic.

? View Code Example
import React, { useState, useTransition } from "react";

function SearchGood() {
  const [query, setQuery] = useState("");
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleChange = (e) => {
    const value = e.target.value;
    setQuery(value); // urgent update (Input reflects immediately)

    startTransition(() => {
      // non-urgent update (Filtering happens in background)
      const filtered = Array(5000)
        .fill("Item")
        .map((x, i) => x + i)
        .filter((x) => x.includes(value));
      setResults(filtered);
    });
  };

  return (
    <div className="p-3">
      <input value={query} onChange={handleChange} />
      {isPending ? <p>Filtering…</p> : <p>Results: {results.length}</p>}
    </div>
  );
}

? Interactive Simulation

Since we can't run full React in this view, the demos below use Vanilla JS to simulate the difference in "feel". Try typing quickly in both inputs.

❌ Blocking (Sticky)

Typing triggers a heavy loop immediately. The browser freezes while calculating.

Lists items will appear here...

✅ Transition (Smooth)

Input updates immediately. The heavy work is deferred (like startTransition).

⏳ isPending: true...
Lists items will appear here...

⚙️ How React Handles Transitions

  • Updates inside startTransition() are treated as non-urgent.
  • React will pause or delay them if the user performs an urgent action (like typing).
  • Urgent updates (like keystrokes) are always rendered immediately.
  • The isPending flag lets you show a loading indicator or skeleton UI while the transition processes.

? Comparison Table

Aspect Normal Update Transition Update
Priority High (urgent) Low (non-urgent)
Effect on UI Can cause lag/freeze Keeps UI responsive
Interruptible No Yes
Ideal For Typing, clicks, inputs Filtering, sorting, heavy renders

? Tips & Best Practices

  • Wrap heavy state updates inside startTransition() to avoid UI lag.
  • Use isPending for lightweight “loading” indicators (like a spinner or dimmed text).
  • Don’t wrap everything — transitions are only for slow, background updates.
  • Combine with useDeferredValue() when you don't have control over the state setter (e.g., props).

? Try It Yourself

  1. Build a live search filter with and without useTransition() to feel the difference.
  2. Add a spinner or text indicator using the isPending boolean.
  3. Profile both versions using the React Profiler to visualize the performance gains.
  4. Use transitions together with Suspense for async data fetching scenarios.