Ryan Soderberg
Ryan Soderberg

Reputation: 772

React Query Optimistic Update causing flashing updates

I'm having an issue with React Query where if a user presses a button too fast, triggering a mutation, the correct value flashes and changes on the screen as the API calls are returned, even when attempting to cancel them. I notice this problem also happens in the official React Query example for optimistic updates. Here's a video I took of the problem happening there.

export const useIncreaseCount = () => {
    const queryClient = useQueryClient()

    return useMutation(
        () => {
            const cart = queryClient.getQueryData('cart') as Cart

            return setCart(cart)
        },
        {
            onMutate: async (cartItemId: string) => {
                await queryClient.cancelQueries('cart')

                const previousCart = queryClient.getQueryData('cart') as Cart

                queryClient.setQueryData(
                    'cart',
                    increaseCount(previousCart, cartItemId)
                )

                return { previousCart }
            },
            onError: (error, _cartItem, context) => {
                console.log('error mutating cart', error)
                if (!context) return
                queryClient.setQueryData('cart', context.previousCart)
            },
            onSuccess: () => {
                queryClient.invalidateQueries('cart')
            },
        }
    )
}

I'm thinking of debouncing the call to use useIncreaseCount, but then onMutate will get debounced, and I don't want that. Ideally just the API call would be debounced. Is there a built in way in React Query to do this?

Upvotes: 2

Views: 2530

Answers (1)

TkDodo
TkDodo

Reputation: 28763

The problem come from the fact that every onSuccess callback calls queryClient.invalidateQueries, even though a different invocation of the mutation is still running. It's the responsibility of the user code to not do that. I see two ways:

  • One thing we are doing sometimes is to track the amount of ongoing mutations with a ref (increment in onMutate, decrement in onSettled), then only call queryClient.invalidateQueries if the counter is zero.
  • assigning mutationKeys and using !queryClient.isMutating(key) should also work.

Upvotes: 5

Related Questions