ProgramGuru

Prioritize Updates with useTransition

Prioritize Updates with useTransition

A teacher needs the letters typed into a search box to appear promptly, even if updating a large result panel takes more work. The input and the report are connected, but they do not have the same urgency.

A transition marks supported state updates as nonurgent rendering work. React can prioritize more urgent interactions over that work. useTransition also exposes a pending flag; it does not move arbitrary JavaScript calculations to another thread.

  1. Separate updates

    Keep the controlled input’s value urgent.

  2. Mark a transition

    Schedule the result selection with startTransition.

  3. Explain pending work

    Distinguish scheduling from fetching and computation.

Follow the user’s interaction

  1. Input

    Typed text updates its own value promptly.

  2. Transition

    The result-query update is marked nonurgent.

  3. Results

    React renders the matching records.

Read this relationship alongside the complete code. This diagram explains the flow; it does not execute React.

Give the input and results separate roles

A controlled input must receive its current value from state and update that state during its change event. Do not place that value update inside a transition. A separate query can drive the result panel as nonurgent work. The temporary separation is deliberate: the text the teacher typed remains available while results catch up.

startTransition runs its callback immediately and marks the appropriate state updates. It is not a timer or a worker. React’s useTransition reference explains supported updates, pending state and controlled-input restrictions.

Urgent text and a transition query

The complete example starts with an empty query and all three names. Typing Maya updates input immediately, while setQuery is marked as transition work. After it completes, Matches: 1 and Maya remain. isPending selects a truthful result-update message.

Three names are too few to demonstrate a reliable perceptible delay, so Updating results may be brief or unseen. The example verifies the scheduling structure and final behavior, not a performance benchmark or a guaranteed duration. It performs no network request.

React · JavaScript with JSX

import React, { useState, useTransition } from 'react';
const students = ['Maya', 'Ravi', 'Asha'];
export default function App() {
  const [input, setInput] = useState('');
  const [query, setQuery] = useState('');
  const [isPending, startTransition] = useTransition();
  const visible = students.filter(name => name.toLowerCase().includes(query.toLowerCase()));
  function change(event) {
    const next = event.target.value;
    setInput(next);
    startTransition(() => setQuery(next));
  }
  return <main><h1>Student search</h1>
    <label>Search names<input value={input} onChange={change} /></label>
    <p role="status">{isPending ? 'Updating results' : 'Results ready'}</p>
    <p>Matches: {visible.length}</p><ul>{visible.map(name => <li key={name}>{name}</li>)}</ul>
  </main>;
}

Expected screen and behavior

Initially: Student search, Search names, Results ready, Matches: 3 and all names.
After searching Maya: Matches: 1 with Maya.

A transition can be interrupted and restarted

React · the input and query setters

import React, { useState, useTransition } from 'react';
const students = ['Maya', 'Ravi', 'Asha'];
export default function App() {
  const [input, setInput] = useState('');
  const [query, setQuery] = useState('');
  const [isPending, startTransition] = useTransition();
  const visible = students.filter(name => name.toLowerCase().includes(query.toLowerCase()));
  function change(event) {
    const next = event.target.value;
    setInput(next);
    startTransition(() => setQuery(next));
  }
  return <main><h1>Student search</h1>
    <label>Search names<input value={input} onChange={change} /></label>
    <p role="status">{isPending ? 'Updating results' : 'Results ready'}</p>
    <p>Matches: {visible.length}</p><ul>{visible.map(name => <li key={name}>{name}</li>)}</ul>
  </main>;
}

React can delay or restart transition rendering when urgent work arrives. Keep component rendering pure so repeated calculation is safe. The eventual screen should describe the latest state, rather than depending on a render having run exactly once.

The filtering happens as part of rendering in this example. A long calculation written directly inside the transition callback would still run immediately and could block the thread before React can help with rendering priorities. For CPU work that cannot be split appropriately, consider a separate computation design such as a worker.

Asynchronous actions still need an ownership policy

React 19 supports asynchronous Actions within transitions, but starting a transition does not validate a response or stop older requests. Request errors, cancellations and outdated results still require an intentional data policy. A later response must not overwrite the teacher’s newer selection merely because both were inside transitions.

When scheduling state updates after await, the current documented limitation can require another startTransition around those updates. Learn that behavior from the current API reference rather than assuming the original callback’s timing automatically covers every later statement. For simple local filtering, keep this lesson’s synchronous transition small.

Worked practice: search without matching letter case

Begin with query ra and display its matching count. The complete answer initializes both the input and result query consistently. Typing ASHA should still produce one match because both the stored name and query are compared in lowercase.

Initializing only one state value would make the displayed field and initial results disagree. The two values have different scheduling roles, but their initial meaning should still agree. Once typing starts, the brief catch-up behavior is expected and is described by pending state.

React · JavaScript with JSX

import React, { useState, useTransition } from 'react';
const students = ['Maya', 'Ravi', 'Asha'];
export default function App() {
  const [input, setInput] = useState('ra');
  const [query, setQuery] = useState('ra');
  const [isPending, startTransition] = useTransition();
  const visible = students.filter(name => name.toLowerCase().includes(query.toLowerCase()));
  function change(event) {
    const next = event.target.value;
    setInput(next);
    startTransition(() => setQuery(next));
  }
  return <main><h1>Student search</h1>
    <label>Search names<input value={input} onChange={change} /></label>
    <p role="status">{isPending ? 'Updating results' : 'Results ready'}</p>
    <p>Matches: {visible.length}</p><ul>{visible.map(name => <li key={name}>{name}</li>)}</ul>
  </main>;
}

Expected screen and behavior

Initially: Search names contains ra; Matches: 1 with Ravi.
ASHA also produces Matches: 1 with Asha.

Transitions do not remove every source of delay

Do not use a transition to control the text input’s own value. Do not expect it to defer a long event-handler loop or to make requests finish faster. A pending label describes the chosen update, not proof that school data was saved.

Transitions can coordinate interface priority; debouncing limits how frequently work starts, and caching reuses previous data. Those are different techniques. Measure the actual interaction before deciding which one helps, and preserve failure and stale-response handling when the result comes from a server.

Check the urgent update

Which update should remain outside startTransition here?
Choose one answer. You can try again.

Answer and explanation

setInput, because it controls the field being typed into.

Correct. Controlled text input updates must remain urgent.

Apply the thread distinction

Does startTransition move a long loop in its callback onto a worker thread?
Choose one answer. You can try again.

Answer and explanation

No; its callback runs immediately on the current execution thread.

Correct. It marks updates rather than relocating arbitrary computation.

Prioritize the interface without hiding the data rules

Keep urgent input state separate from transition-driven result state, render purely and interpret pending feedback according to the work it represents. Transitions improve scheduling opportunities; they do not create workers, cache data or prevent stale requests.

Review performance measurement and request ownership before applying the same idea to a larger student search.

Reaching the recap and completing the quizzes are separate milestones.

    Next lesson: useActionState: Form Results and Pending Actions →