useOptimistic & use()
Instant UI before the server confirms. Promise unwrapping.
Two more React 19 hooks worth knowing about: useOptimistic and use. The first makes "like" buttons feel instant. The second is a strange little hook that can unwrap promises and read contexts conditionally. Both are quietly transformative.
useOptimistic: feel the update before the server agrees
The classic problem: user clicks "Like." You fire a request to the server. The button doesn't look pressed until the response comes back. Even on a fast network that's 100 ms of perceived lag. Optimistic UI fixes it by updating the screen immediately and rolling back only if the request fails.
import { useOptimistic, useState, useTransition } from "react";
function Likes({ initial }) {
const [likes, setLikes] = useState(initial);
const [optimisticLikes, addOptimistic] = useOptimistic(
likes,
(current, delta) => current + delta
);
const [, startTransition] = useTransition();
async function handleLike() {
startTransition(async () => {
addOptimistic(1); // UI updates immediately
const next = await api.like(); // round-trip
setLikes(next); // real state catches up
});
}
return <button onClick={handleLike}>โฅ {optimisticLikes}</button>;
}useOptimistic(realValue, reducer)returns a "pretend value" that you can update synchronously. After the transition completes, React automatically reverts the optimistic value and uses whatever the real state is now. If the action fails and you don't update real state, the optimistic value vanishes and the UI snaps back.
use(promise): unwrap async values in Suspense
The usehook is React's answer to "just give me the value, I don't want to write isLoading checks." Pass it a promise, and the component suspends until the promise resolves. Pass it a context, and you get the context value. The trick: use can be called conditionally. Unlike every other hook.
import { use, Suspense } from "react";
function UserName({ userPromise }) {
const user = use(userPromise); // suspends here until promise resolves
return <h1>Hello, {user.name}</h1>;
}
function App() {
const userPromise = fetchUser(); // create the promise
return (
<Suspense fallback={<p>Loading...</p>}>
<UserName userPromise={userPromise} />
</Suspense>
);
}While the promise is pending, React shows the nearest Suspense fallback. When it resolves, the component renders with the value. No useEffect, no loading state, no error checks (use ErrorBoundary for those).
use(context): the conditional context read
useContext can only be called unconditionally at the top of a component. use is the exception: you can call it inside an if, a loop, or anywhere else.
function Greeting({ showName }) {
if (showName) {
const user = use(UserContext); // legal!
return <h1>Hi {user.name}</h1>;
}
return <h1>Hi friend</h1>;
}This sounds small but it's big. Component code that reads context only sometimes used to require restructuring. Now you just write the conditional and call use.
Try it: optimistic like button
Click the heart. Notice it ticks up instantly, even though the fake server takes 800 ms. Try clicking rapidly; the UI batches optimistic updates while the real count catches up.
Quiz
What happens to the optimistic value if your transition fails?
Recap
useOptimisticlets you show a hypothetical state during an async action. Reverts when the transition ends if not committed.- Pair it with
useTransition+ a real state setter to make the optimistic update permanent on success. use(promise)suspends until resolved. The nearestSuspenseshows its fallback.use(context)can be called conditionally, unlikeuseContext.- Optimistic UI is one of the highest-impact UX upgrades. Apply it generously to actions where the success path is the common case.