Race condition with optimistic updates and polling/refetch on focus
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):
- Wrap the query hook at a global level and use
skipif 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 whenskipis 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
})
}
- Try using the
mergefunctionality. Unfortunately an error is thrown if I try to usestore.getState()it within themergefunction.getStateis not exposed in themergecallback 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.
Yeah, amazing how much timing issues are a problem around this type of work :)
Was planning to spend some time on issues tomorrow - I'll take a look!
Spent some time working on this. Made a bunch of local changes around things like tracking "data version" in the cache, thought I had something working.
Now I'm actually a bit confused and want to take a step back :)
Can you talk me through both the actual behavior you're seeing, and what you would expect or want the behavior to be instead? Like, the sequence of events and state changes?
Would you expect the polling refetch to be ignored completely and not update the state?
Current behavior:
- I trigger a mutation and apply an optimistic update on "get all campaign metadata" endpoint.
- Polling causes a refetch of "get all campaign metadata" (very fast endpoint) before my mutation completes (extremely slow POST endpoint).
- The optimistic update I made is lost due to the refetch (old value is redisplayed).
- The mutation (slow POST endpoint) finally finishes and cache invalidation (via provides/invalidates tags) causes a refetch of "get all campaign metadata".
- Up-to-date value is shown once refetch triggered by cache invalidation finishes (new value is redisplayed again).
Expected behavior:
- I trigger a mutation and apply an optimistic update on "get all campaign metadata" endpoint.
- Polling causes a refetch of "get all campaign metadata" (very fast endpoint) before my mutation completes (extremely slow POST endpoint).
- The optimistic update is somehow preserved while the mutation continues to run, regardless of additional refetches that occur in the process.
I agree that I wouldn't expect the fresh data to be completely ignored. Just spitballing, but maybe the fresh data could be used, but with patches (if any) from any in-flight mutations re-applied to it.