Skip to content

Race condition with optimistic updates and polling/refetch on focus #5139

@agusterodin

Description

@agusterodin

I'm experiencing an issue where if I apply an optimistic update when performing a mutation (slow POST endpoint) and a polling refetch (on a fast GET endpoint) occurs after the optimistic update but while the mutation is still in process, the optimistic update gets lost and the UI flickers back and forth.

Quick polling intervals (eg: 100) make this really easy to recreate.

The same issue (optimistic update gets lost) occurs on "refetch on focus" as well.


Ideas on possible solutions (none of which have worked):

  1. Wrap the query hook at a global level and use skip if mutation is in progress. Unfortunately this doesn't work. The optimistic update doesn't become visible in any part of the application using the hook when skip is true.
export const getCampaignsApi = (state: State) => state.campaignsApi

export const getMutationInProgress = createSelector([getCampaignsApi], campaignsApi => {
  return Object.values(campaignsApi.mutations).some(mutation => mutation?.status === 'pending')
})

export function useGetAllCampaignMetadataQuery(options?: UseQueryOptions) {
 const mutationInProgress = useSelector(getMutationInProgress)
 return campaignApiSlice.useGetAllCampaignMetadataQuery(undefined, {
   skip: mutationInProgress,
   ...options
 })
}
  1. Try using the merge functionality. Unfortunately an error is thrown if I try to use store.getState() it within the merge function. getState is not exposed in the merge callback so I would have no other way of accessing the current state.
export function getMutationInProgress() { {
  const state = store.getState()
  return Object.values(campaignsApi.mutations).some(mutation => mutation?.status === 'pending')
}

getAllCampaigns: builder.query({
  // other parts of endpoint definition omitted
  merge: (currentCacheData, responseData) => {
    const mutationInProgress = getMutationInProgress()
    if (mutationInProgress) {
      return currentCacheData
    }
    else {
      return responseData
    }
})

Video Screenshots

Polling
https://github.com/user-attachments/assets/7627e4c4-3460-480c-9db8-0c6003a96bcf

Refetch on focus
https://github.com/user-attachments/assets/cd9557ad-5559-464b-82ce-4640673c9fbc


Minimal reproduction

Unfortunately i'm not able to set up a fully working example due to the fact that a simple mock server that serves the same mock response every time wouldn't be able to save the changes of a mutation (since I would need a proper database to do so). The real endpoints i'm using on my project require authentication. That being said, here is the stripped down code I used for my video example:

https://github.com/agusterodin/rtkq-playground/tree/optimistic-update-polling-race-condition

To run this use pnpm i and pnpm dev. I have the polling and refetch on focus lines commented out but had them uncommented when taking their respective video screenshots.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions