useTransition vs useOptimistic

Published: (December 8, 2025 at 06:00 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

Cover image for useTransition vs. useOptimistic

The Dynamic Duo: Managing Process vs. Managing Perception

To achieve the “illusion” of speed, you need two distinct tools:

  • useTransition – Handles the background work. It prevents the browser from freezing and tells you if work is happening (isPending).
  • useOptimistic – Handles the immediate visual feedback. It updates the UI instantly, assuming the background work will succeed.

The Scenario: A “Like” Button

Imagine a user clicks a “Like” button on a post.

  • Without Optimistic UI:
    User clicks → waits 1 second → the number goes up. (Feels sluggish).

  • With Optimistic UI:
    User clicks → the number goes up instantly → the server catches up in the background. (Feels snappy).

Standard Approach (useTransition only)

Here we rely on the server to tell us the new like count. useTransition is used only to show a loading spinner so the user knows something is happening.

Experience: User clicks → Spinner appears → Spinner vanishes & number updates.

'use client';
import { useTransition } from 'react';
import { incrementLike } from './actions'; // Server Action

export default function StandardLikeButton({ likeCount }) {
  const [isPending, startTransition] = useTransition();

  const handleClick = () => {
    startTransition(async () => {
      // 1. Wait for the server to finish
      await incrementLike();
      // 2. UI updates after the server responds and the page re‑renders
    });
  };

  return (
    {/* Show a loading state because we don't have the new data yet */}
    {isPending ? 'Updating...' : `♥ ${likeCount} Likes`}
  );
}

Illusion Approach (useTransition + useOptimistic)

We predict the future. The UI is updated immediately, while the server request runs in the background. useTransition keeps the app responsive; useOptimistic provides the instant visual change.

Experience: User clicks → Number updates instantly (no spinner needed).

'use client';
import { useOptimistic, useTransition } from 'react';
import { incrementLike } from './actions';

export default function OptimisticLikeButton({ likeCount }) {
  // Optimistic state: real data + reducer to calculate the "fake" new state
  const [optimisticLikes, addOptimisticLike] = useOptimistic(
    likeCount,
    (currentState, optimisticValue) => currentState + optimisticValue
  );

  const [isPending, startTransition] = useTransition();

  const handleClick = () => {
    startTransition(async () => {
      // 1. ILLUSION: Update the UI immediately
      addOptimisticLike(1);

      // 2. REALITY: Perform the actual server request in the background
      // If this fails, React automatically rolls back the optimistic state!
      await incrementLike();
    });
  };

  return (
    {/* Display the optimistic (predicted) value immediately */}
    ♥ {optimisticLikes} Likes
  );
}

Key Differences Summary

FeatureuseTransitionuseOptimistic
Primary GoalPrevent UI freezing; track “loading” statusShow the result instantly (before server confirm)
User Experience“Please wait…” (honest delay)“Done!” (instant gratification)
Data SourceWaiting for server dataClient‑side prediction
If Server Fails?App stays in previous state or shows an errorUI automatically reverts to correct server state

Why Use Them Together?

useOptimistic is rarely useful without useTransition (or Server Actions that wrap transitions).

  • useOptimistic handles the data (e.g., showing 101 likes instead of 100).
  • useTransition handles the execution (ensuring the button click doesn’t freeze the page while the request travels to the server).

Combining both creates a fast, smooth user experience.

Back to Blog

Related posts

Read more »