Skip to main content

Optimistic UI Rollback: Issues and Solution in React Query (React Native)

Problem Encountered

When implementing optimistic UI for movie swiping, we wanted to:

  • Remove the card from the stack immediately on swipe (optimistic update)
  • If the API call fails, restore the swiped card to the stack (rollback)

Challenge:

  • How do we know which card to restore if the mutation fails, especially if the user has swiped multiple times quickly?
  • How do we pass the swiped movie data to the error handler in a robust, React Query-compatible way?

Issues Faced

  • React Query's useMutation does not support a variables or context property in the mutate options directly.
  • The onError callback receives the mutation variables as the second argument, but not the full movie object unless you pass it yourself.
  • If you try to use a closure variable (like topMovie), it may be stale or incorrect if the user swipes quickly.

Solution: Use the context Pattern

React Query's mutate function allows you to pass a context object via the onMutate callback. This context is then available in onError and onSettled.

Pattern:

  1. In onMutate, return the swiped movie as context.
  2. In onError, use the context to restore the card.

Example:

const swipeMutation = useMutation({
mutationFn: async (swipeData) => { /* ... */ },
onMutate: (swipeData) => {
// Find the movie being swiped
const swipedMovie = cardStack[cardStack.length - 1]
setCardStack((prev) => prev.slice(0, -1)) // Optimistically remove
return { swipedMovie }
},
onError: (error, variables, context) => {
setError(error.message || 'Unknown error')
if (context?.swipedMovie) {
setCardStack((prev) => [...prev, context.swipedMovie]) // Restore
}
},
onSuccess: () => setError(null),
})

const handleSwipe = (direction) => {
// ...determine liked/skipped/score...
swipeMutation.mutate({ movie_id, liked, skipped, score })
}

Why This Way?

  • Robust: The context pattern ensures you always restore the correct card, even if the user swipes quickly or out of order.
  • Idiomatic: This is the recommended approach in React Query for optimistic updates and rollback.
  • Decoupled: You don't rely on closure variables or global state, which can be error-prone.

References


This doc explains the technical reasoning and solution for robust optimistic UI rollback in our React Native swiping stack.