DevAcademy
LearnReactuseTransition
IntermediateReact

useTransition

Learn how to mark a state update as a low-priority transition, keeping the UI responsive during expensive re-renders.

Reading Time

16 min

Lesson

Lesson 22 of 42

The Problem: Slow Updates Block the UI

By default, every state update is treated as urgent — React tries to apply it right away. If that update triggers an expensive re-render (like re-filtering a huge list), the UI can feel like it’s freezing, since React doesn’t interrupt that work to keep something else (like a text input) feeling responsive.

What useTransition Does

useTransition lets you mark a state update as a "transition" — lower priority than urgent updates like typing — so React can keep the UI responsive and interrupt the transition’s work if something more urgent comes in.

A Search Box That Stays Responsive

import { useState, useTransition } from "react";

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

  function handleChange(event) {
    const value = event.target.value;
    setQuery(value); // urgent: keeps the input feeling instant

    startTransition(() => {
      // low-priority: filtering a huge list won't block typing
      setResults(allItems.filter((item) => item.includes(value)));
    });
  }

  return (
    <>
      <input value={query} onChange={handleChange} />
      {isPending && <p>Updating results...</p>}
      <ResultsList items={results} />
    </>
  );
}

useTransition Return Value

ItemPurpose
isPendingtrue while the transition is still processing in the background
startTransitionWraps a state update, marking it as low-priority

Transitions Can Be Interrupted

If the user keeps typing while a transition is still processing an older value, React abandons the stale, in-progress transition and starts a new one with the latest value — the UI never falls behind or shows outdated results from an interrupted update.

startTransition Only Wraps State Updates

Only the state updates called synchronously inside the function passed to startTransition are treated as a transition. An async operation like a fetch call inside it is not itself part of the transition — only the setState calls that happen after it resolves.

useTransition vs useDeferredValue

useTransition marks the *update itself* as low priority (used when you control the event that triggers the update, like an onChange). useDeferredValue instead takes an already-changing value and lazily defers using it (useful when you don’t control where the value comes from, like a prop).

Best Practice

Reach for useTransition when a specific state update you control (in an event handler) triggers an expensive re-render, and you want the rest of the UI to stay interactive while it processes.

Interview Questions

Quick Quiz

1. What does startTransition let you do?

2. What does the isPending flag from useTransition indicate?

3. What happens if the user triggers a new transition while an older one is still processing?