Optimistic UI Updates and Error Recovery for a Great User Experience
Overview
Optimistic UI updates are a modern UX pattern that allows your app to feel fast and responsive by updating the interface immediately, before waiting for a server response. This is especially important in mobile and React Native apps, where network latency can otherwise make the UI feel sluggish or unresponsive.
Error recovery complements optimistic updates by providing a way to gracefully handle failures—allowing users to retry or undo actions if something goes wrong.
This guide covers best practices and implementation examples for both web and React Native, aimed at developers building high-quality, industry-standard user experiences.
Why Use Optimistic UI Updates?
- Instant feedback: Users see the result of their action immediately, not after a network round-trip.
- Non-blocking: The UI remains interactive; users can continue to swipe, tap, or navigate while requests are in flight.
- Modern feel: This is the standard for top-tier apps (e.g., Tinder, Instagram, Twitter).
General Pattern
- Update the UI immediately as if the server request will succeed (optimistic update).
- Send the API request in the background.
- If the request succeeds: Do nothing (the UI is already correct).
- If the request fails:
- Show an error message.
- Offer a way to retry or undo the action (e.g., a "Retry" button or restoring the previous state).
Example: Movie Swiping (React Native)
Optimistic Swipe Logic
// Inside your SwipeStack component
const handleSwipe = (direction) => {
// 1. Optimistically remove the card from the stack
setCardStack((prev) => prev.slice(0, -1))
// 2. Send the API request in the background
swipeMutation.mutate(
{ movie_id, liked, score },
{
onError: () => {
// 3. If it fails, restore the card and show an error
setCardStack((prev) => [...prev, swipedMovie])
setError("Failed to record swipe. Please try again.")
},
}
)
}
Error Recovery UI
{
error && (
<View style={styles.errorBanner}>
<Text>{error}</Text>
<TouchableOpacity onPress={retryLastSwipe}>
<Text>Retry</Text>
</TouchableOpacity>
</View>
)
}
Example: Web (React)
The same pattern applies for web apps using React Query or similar libraries:
const mutation = useMutation(recordSwipe, {
onMutate: (swipe) => {
// Optimistically update local state/UI
removeCardFromStack(swipe.movie_id)
},
onError: (error, swipe, context) => {
// Roll back the optimistic update
restoreCardToStack(swipe.movie_id)
showToast("Failed to record swipe. Please try again.")
},
})
Best Practices
- Never block the UI while waiting for a network response.
- Always provide a way to recover from errors (retry, undo, or restore previous state).
- Show clear feedback (e.g., loading indicators, error banners, toasts).
- Log errors for diagnostics, but don’t overwhelm the user with technical details.
- Test on slow networks to ensure the experience remains smooth.
Further Reading
This document is part of our Great UX Practices series. For more, see the /docs/ux/ section.