Fake It Till You Make It, in React 19
by Morteza on 1/14/2025
React 19 introduced a bunch of cool features, but let’s be honest, nothing screams “fake it till you make it” quite like the new useOptimistic hook. This delightful little utility allows you to lie to your users in the best possible way—by giving them a preview of success before it actually happens.
In this post, we’re going to dive into the mechanics of useOptimistic, explore how it works, and build a wishlist app to make your wildest optimistic dreams come true.
What is useOptimistic?
React’s useOptimistic hook lets you:
- Show your users a different state while an async action is in progress.
- Update the UI immediately based on an “optimistic” assumption of the final state.
This is particularly useful when performing tasks like form submissions, where waiting for the server’s response is so last season. Instead, we update the UI instantly and make the app feel snappier.
Here’s how it works:
const [optimisticState, addOptimistic] = useOptimistic(state, updateFn);
- state: The current state (e.g., your actual data).
- updateFn(currentState, optimisticValue): A pure function that merges the current state with the optimistic value to produce the optimistic state.
- optimisticState: The resulting optimistic state that the UI will use during an async operation.
- addOptimistic: A dispatcher function that triggers an optimistic update.
Let’s Build Something Optimistic
Imagine a wishlist app where users can add items they want. But instead of making them wait while we send a request to the backend, we’ll immediately show the new item in their wishlist—because that’s the kind of instant gratification we all need.
Here’s our code:
import React, { useOptimistic, useState } from "react";
export default function App() {
const [cart, setCart] = useState([]);
const [optimisticCart, optimisticAddToCart] = useOptimistic(
cart,
(state, item) => {
return [...state, item];
}
);
const formAction = async (formData) => {
const wishInputValue = String(formData.get("wishInput"));
const itemId = String(formData.get("itemID"));
const item = { id: itemId, title: wishInputValue };
// Optimistically add the item
optimisticAddToCart(item);
// Simulate a network request
await new Promise((resolve) => setTimeout(resolve, 1500));
// Update the real cart
setCart((cart) => [...cart, item]);
};
console.log({ cart, optimisticCart });
const cartForDisplay = optimisticCart;
return (
<div className="container">
<div className="app-container">
<form action={formAction} className="wish-form">
<h1 className="heading">The best ever wish list</h1>
<input type="hidden" name="itemID" value={"1zxda"} />
<input
className="wish-input"
type="text"
name="wishInput"
placeholder="Enter your wish"
required
/>
<button type="submit" className="submit-btn">
I need this
</button>
</form>
</div>
<div className="wishlist-container">
<h2>Your wish list:</h2>
<ul className="wishlist">
{cartForDisplay.map((item, index) => {
const isPending = !cart.includes(item);
return (
<li key={index} style={{ opacity: isPending ? 0.5 : 1 }}>
{item.title}
</li>
);
})}
</ul>
</div>
</div>
);
}
What’s Happening Here?
- State Setup:
- cart holds the real state (what’s actually saved).
- optimisticCart gives us the UI-ready state that includes optimistic updates.
- Optimistic Updates:
- The optimisticAddToCart function pretends an item is already added while the app waits for the backend.
- UI Feedback:
- Items that are still pending are displayed with reduced opacity.
Why You’ll Love It
- Instant Gratification: Users don’t have to wait to see the results of their actions.
- Better UX: The app feels snappier and more responsive.
- Less Rage Clicking: Users won’t hit the button five times because they think nothing’s happening.
Final Thoughts
The useOptimistic hook is like the ultimate poker face for your app. It lets you confidently show results while doing the heavy lifting in the background. So, next time you’re building something that involves async operations, give this hook a try and watch your UI turn into a responsive powerhouse.
Because, let’s face it, sometimes it’s okay to fake it till you make it—as long as the backend doesn’t call your bluff!