-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Description
Describe the bug
I have created a very contrived example reproduction code that you can find here:
https://github.com/franleplant/react-query-unresolved-promises/blob/master/src/App.tsx
I say contrived because it doesn't represent real use case but I first found this error
in a closed source company app where, as you probably know, things started to get messy
and there were some interactions between mutation's onSuccess and refetchQueries.
To be a bit more concrete, when we save EntityA we refetch EntityA (because of the way our particular backend works) but since we weren't using exact: true it was causing it to refetch EntityA1 via a query that wasn't initialized, so by a semi complex interactions of the query of EntityA we also did setQueryData of EntityA1 which caused the problem I explain below.
(Sorry, it ended up not being that much more concrete)
I found this bug in react-query 1.x but I have noticed the same code in 2.x
Expected behavior
A mutation promise always resolves to something, either a value or an error.
Desktop (please complete the following information):
- OS: macos
- Browser all
Additional context
I have isolated the bug to an edge case interaction between queryCache.setQueryData and queryCache.refetchQuery
(extracted from the repro repo)
export function useSomeMutation() {
return useMutation<void, undefined>(
async () => {
await delay();
},
{
onSuccess: async () => {
console.log("onSuccess start");
queryCache.setQueryData("notInstantiated", "hello"); //BANG
queryCache.setQueryData("notInstantiated2", "hello"); // BANG
const a = queryCache.refetchQueries("notInstantiated", { force: true });
const b = queryCache.refetchQueries("notInstantiated2", {
force: true,
});
console.log("promises are never resolved", a, b);
await Promise.all([a, b]);
// This never reaches
console.log("onSuccess end");
},
}
);
}Whenever you try to setQueryData on a query that hasn't been instantiated react-query gives the queryFn a value of new Promise(noop) which is a promise that never resolves.
So when you later do refetchQuery of that same query you get a promise that never returns and since useMutation waits for the onSuccess async function to resolve then it never resolves.
We are virtually saying this
onSuccess: () => new Promise(noop)- check the 1.x code for setQueryData
- check the 1.x code for refetchQuery
This same default value of new Promise(noop) is used in 2.x branch.
for lib consumers: A workaround is to check that the query that you are trying to setData on has actually an instance
// check that there are current instances of `myQueryKey`
// i.e. some component has used it
if (queryCache.getQuery(myQueryKey)) {
queryCache.setQueryData(myQueryKey, someData)
}But I wander why we are giving a default value of a promise that never resolves. Can you we change it to Promise.resolve() ? It seems that the functionality will be maintained but in these sort of edge cases we wont get a promise that never resolves.
Let me know what you think @tannerlinsley and I can definitively help with this in both 1.x and 2.x branches.