The Bug That Taught Me About React Query and Optimistic Updates
I was working on the Issue Tracker project when I encountered a frustrating bug: when two users tried to update the same issue simultaneously, one user's changes would disappear.
The Problem
The app used React Query for data fetching, but I wasn't handling concurrent updates properly. When User A updated an issue, User B's screen would show stale data, and if User B saved, it would overwrite User A's changes.
The Investigation
I spent hours debugging this. The issue wasn't with React Query itself, but with how I was handling the cache updates. I was using queryClient.setQueryData incorrectly, which caused race conditions.
The Solution
I implemented optimistic updates with proper error handling:
const mutation = useMutation({
mutationFn: updateIssue,
onMutate: async (newIssue) => {
await queryClient.cancelQueries(['issues', newIssue.id])
const previousIssue = queryClient.getQueryData(['issues', newIssue.id])
queryClient.setQueryData(['issues', newIssue.id], newIssue)
return { previousIssue }
},
onError: (err, newIssue, context) => {
queryClient.setQueryData(['issues', newIssue.id], context.previousIssue)
},
onSettled: () => {
queryClient.invalidateQueries(['issues'])
}
})Key Learnings
- Optimistic updates improve UX: Users see changes instantly, making the app feel faster
- Error handling is crucial: Always rollback on error to maintain data consistency
- Cache invalidation strategy: Know when to invalidate vs. when to update directly
Results
- Zero data loss in concurrent update scenarios
- Instant UI feedback for better user experience
- Proper error recovery that maintains data integrity
This bug taught me that real-time synchronization requires careful consideration of edge cases, and React Query provides excellent tools to handle them.