Software Galaxy

Software Galaxy

Async Transition in React 19

Vivek's avatar
Vivek
Sep 13, 2025
∙ Paid
Share

React 19 introduces a suite of features that empower developers to build seamless, performant user interfaces. Among these, async transitions stand out as a game-changer for handling asynchronous operations without freezing the UI. This is particularly impactful when building forms where users expect instant feedback, smooth interactions, and no jarring loading states.

In this blog post, we’ll dive deep into how async transitions in React 19 enable non-blocking form experiences, explore practical examples, and provide best practices for leveraging this feature to create seamless and delightful user interfaces.

By the end, you’ll understand how to use useTransition and related APIs to handle form submissions, data fetching, and state updates asynchronously while keeping your UI responsive. Let’s get started!

What Are Async Transitions in React 19?

React’s useTransition hook, first introduced in React 18, was refined in React 19 to make managing asynchronous state updates even more intuitive. Async transitions enable you to mark certain state updates as non-urgent, allowing React to prioritize rendering critical UI changes (such as user input) while deferring less urgent tasks (like fetching data or updating derived state).

When applied to forms, async transitions ensure that users can continue interacting with the UI — typing, clicking, or navigating — while background tasks like API calls or complex computations run without blocking the main thread. This results in a smoother, more responsive experience, even on low-powered devices or slow networks.

Here are some key benefits of async transitions for forms:

  • Non-blocking UI: Users can keep interacting with the form while async operations complete.

  • Optimistic updates: You can show immediate feedback based on expected outcomes, then reconcile these with server responses.

  • Granular control: Distinguish between urgent and non-urgent updates for better performance.

  • Error handling: Gracefully manage errors during async operations without crashing the UI.

Setting the Stage: A Sample Form

To illustrate async transitions, let’s build a user profile form where users can update their name, email, and bio. The form submits data to a mock API, which may take a few seconds to respond. Without async transitions, the UI might freeze during submission, frustrating users. With React 19’s tools, we’ll ensure the form remains interactive and provides clear feedback.

Here’s the basic setup:

import { useState } from "react";

function ProfileForm() {
  const [formData, setFormData] = useState({
    name: "",
    email: "",
    bio: "",
  });

  const handleSubmit = async (e) => {
    e.preventDefault();
    // Simulate API call
    await new Promise((resolve) => setTimeout(resolve, 2000));
    console.log("Submitted:", formData);
  };

  const handleChange = (e) => {
    setFormData({ ...formData, [e.target.name]: e.target.value });
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name:
        <input
          type="text"
          name="name"
          value={formData.name}
          onChange={handleChange}
        />
      </label>
      <label>
        Email:
        <input
          type="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
        />
      </label>
      <label>
        Bio:
        <textarea name="bio" value={formData.bio} onChange={handleChange} />
      </label>
      <button type="submit">Save</button>
    </form>
  );
}

This form works, but has a problem: during the 2-second API call, the UI might feel sluggish, especially if the user tries to interact with it. Let’s enhance it with async transitions.

Introducing useTransition

The useTransition hook lets you wrap state updates in a transition, telling React to treat them as low priority. It returns an array with two values:

  • startTransition: A function to wrap non-urgent state updates.

  • isPending: A boolean indicating whether the transition is in progress.

Here’s how we can refactor the form for useTransition:

import { useState, useTransition } from "react";

function ProfileForm() {
  const [formData, setFormData] = useState({
    name: "",
    email: "",
    bio: "",
  });
  const [isPending, startTransition] = useTransition();
  const [status, setStatus] = useState(null);

  const handleSubmit = async (e) => {
    e.preventDefault();
    startTransition(async () => {
      try {
        // Simulate API call
        await new Promise((resolve) => setTimeout(resolve, 2000));
        setStatus("Success! Profile updated.");
      } catch (error) {
        setStatus("Error updating profile.");
      }
    });
  };

  const handleChange = (e) => {
    setFormData({ ...formData, [e.target.name]: e.target.value });
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name:
        <input
          type="text"
          name="name"
          value={formData.name}
          onChange={handleChange}
        />
      </label>
      <label>
        Email:
        <input
          type="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
        />
      </label>
      <label>
        Bio:
        <textarea name="bio" value={formData.bio} onChange={handleChange} />
      </label>
      <button type="submit" disabled={isPending}>
        {isPending ? "Saving..." : "Save"}
      </button>
      {status && <p>{status}</p>}
    </form>
  );
}

What’s Happening Here?

  1. Wrapping the Submission: The handleSubmit function uses startTransition to mark the async operation (API call and status update) as a low-priority task. This ensures the UI remains responsive while the API call runs.

  2. Pending State: The isPending boolean lets us disable the submit button and show a “Saving…” message during the transition, providing visual feedback.

  3. Error Handling: We handle potential errors gracefully, updating the status message without blocking the UI.

  4. Immediate Input Updates: Because setFormData (called in handleChange) isn’t wrapped in a transition, input changes are applied instantly, keeping the form interactive.

The result? Users can keep typing or interacting with the form while the submission happens in the background. No freezes, no jarring delays.

Optimistic Updates for Instant Feedback

One powerful pattern with async transitions is optimistic updates, where you update the UI immediately based on the expected outcome, then reconcile with the server response. This makes the app feel faster and more responsive.

Let’s modify our form to show an optimistic “Profile updated!” message before the API call completes:

This post is for paid subscribers

Already a paid subscriber? Sign in
© 2025 Vivek
Privacy ∙ Terms ∙ Collection notice
Start writingGet the app
Substack is the home for great culture