useTransition vs useOptimistic
Source: Dev.to

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
| Feature | useTransition | useOptimistic |
|---|---|---|
| Primary Goal | Prevent UI freezing; track “loading” status | Show the result instantly (before server confirm) |
| User Experience | “Please wait…” (honest delay) | “Done!” (instant gratification) |
| Data Source | Waiting for server data | Client‑side prediction |
| If Server Fails? | App stays in previous state or shows an error | UI automatically reverts to correct server state |
Why Use Them Together?
useOptimistic is rarely useful without useTransition (or Server Actions that wrap transitions).
useOptimistichandles the data (e.g., showing 101 likes instead of 100).useTransitionhandles 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.