T
TanStack3y ago
adverse-sapphire

Dependent mutations with queryClient.setMutationDefaults

I want to give offline support for a given feature that I had on my application. That feature is a simple for lm that allows to fill a title and N images. Both are made with different requests which the last uses the former result ID. To give offline support to this feature, we would need to move the callbacks as queryClient.setMutationDefaults for the fact that can be resumed even if the mutation is not mount. To accomplish that, how can I have two queryClient.setMutationDefaults which one depends on the result of the other?
12 Replies
conscious-sapphire
conscious-sapphire3y ago
what do you mean "result of the other" setMutationDefaults doesn't return anything
adverse-sapphire
adverse-sapphireOP3y ago
Sorry for not explaining it well. We have a form that allows the user to fill the title field and upload images. If the user fills the title and the image, the process is the following: - Creates the task (POST /task) - then we use an external provider to create a signedId and we call an endpoint to edit the previous task with the image (POST /tasks/:taskId/task_images) so, the corresponding mutations would be something like this:
export const useCreateTask = () => {
return useMutation(apiCreateTask);
};

export const useUploadTaskImage = () => {
return useMutation(apiUploadTaskImage);
};
export const useCreateTask = () => {
return useMutation(apiCreateTask);
};

export const useUploadTaskImage = () => {
return useMutation(apiUploadTaskImage);
};
Without thinking in offline support, this could be done as the following.
Component.tsx

const [images, setImages] = useState([])
const { mutation: createTask } = useCreateTask()
const { mutation: uploadTaskImage } = useUploadTaskImage()

createTask({
onSuccess: (data) => {
images.forEach(() => uploadTaskImage({ taskId: data.id, ...image})
}
})
Component.tsx

const [images, setImages] = useState([])
const { mutation: createTask } = useCreateTask()
const { mutation: uploadTaskImage } = useUploadTaskImage()

createTask({
onSuccess: (data) => {
images.forEach(() => uploadTaskImage({ taskId: data.id, ...image})
}
})
Now thinking in moving this to support offline mode. As documentation suggests, we need to defined the mutation defaults.
queryClient.setMutationDefaults(['create', 'task'], { mutationFn: apiCreateTask })
queryClient.setMutationDefaults(['create', 'task', 'image'], { mutationFn: apiUploadTaskImage })
queryClient.setMutationDefaults(['create', 'task'], { mutationFn: apiCreateTask })
queryClient.setMutationDefaults(['create', 'task', 'image'], { mutationFn: apiUploadTaskImage })
The problem is, if, as a User, I create a new task with images, the ['create', 'task'] will be set as paused mutation, and if I close the app and return, only the ['create', 'task'] mutation runs and don't know how to create a relation between the task creation and image upload when resuming mutations. Do I need to have a different mutation default defined which encapsulate all the logic of creating and uploading the image? The expected result when the user came online or return to the application would be: 1. Create task 2. Upload image to the previous created task TIA.
conscious-sapphire
conscious-sapphire3y ago
why can't you add the onSuccess handler also to setMutationDefaults ?
adverse-sapphire
adverse-sapphireOP3y ago
As far as I know, we are not able to use hooks on setting mutation default. Are you suggesting to set the mutation default inside of one of the root components?
conscious-sapphire
conscious-sapphire3y ago
ah, I see, uploadTaskImage is a .mutate from a different mutation. I guess I'd just invoke the underlying request method in the callback instead. mutations are only valuable if you need to track the lifecycle (pending / success / error)
adverse-sapphire
adverse-sapphireOP3y ago
The thing is that we want to handle each request separately in case if any of them throws an error, I don't want to retry all the process from the beginning (if I had everything inside in a single callback).
conscious-sapphire
conscious-sapphire3y ago
i don't understand that. Somewhere, you must have a function like:
async function doUploadTaskImage(body) => axios.post(....)
async function doUploadTaskImage(body) => axios.post(....)
and you'd just call that in the loop instead of the mutation:
createTask({
onSuccess: (data) => {
images.forEach(() => doUploadTaskImage({ taskId: data.id, ...image})
}
})
createTask({
onSuccess: (data) => {
images.forEach(() => doUploadTaskImage({ taskId: data.id, ...image})
}
})
adverse-sapphire
adverse-sapphireOP3y ago
We are somehow stuck to the images mutation because we are tracking the status of each request in order to present each image upload status separately. We don't want to use the API method directly for the reason that we would loose all the side effects present in the mutation. And also, by having the mutation, we will have the retry and offline mechanism for free
conscious-sapphire
conscious-sapphire3y ago
okay. I don't know a fix right now
adverse-sapphire
adverse-sapphireOP3y ago
Is it good practice to define setMutationDefault inside useEffect of a root component? Example:
const queryClient = useQueryClient() // Or using queryClientInstance
useEffect(() => {
if (!resumePausedMutationsReady) return;
queryClient.setMutationDefaults(createIssueMutationKey!, createIssueMutationOptions);
queryClient.setMutationDefaults(uploadIssueImageKey!, uploadIssueImageOptions);

queryClient.resumePausedMutations()
}, [resumePausedMutationsReady]);
const queryClient = useQueryClient() // Or using queryClientInstance
useEffect(() => {
if (!resumePausedMutationsReady) return;
queryClient.setMutationDefaults(createIssueMutationKey!, createIssueMutationOptions);
queryClient.setMutationDefaults(uploadIssueImageKey!, uploadIssueImageOptions);

queryClient.resumePausedMutations()
}, [resumePausedMutationsReady]);
conscious-sapphire
conscious-sapphire3y ago
yeah it can work
adverse-sapphire
adverse-sapphireOP3y ago
Just having some weird behaviour with this current code. Sometimes when resuming, the no mutation found error is triggered. Any idea? The only way I got this working was doing the following:
// App.ts
<PersistQueryClientProvider
client={queryClient}
persistOptions={{ persister }}
onSuccess={() => {
setTimeout(() => queryClient.resumePausedMutations(), 0);
}}
>
<AppContent />
</PersistQueryClientProvider>

// AppContent.tsx
const { mutationKey: createIssueMutationKey, ...createIssueMutationOptions } =
useCreateIssueOptions();
const { mutationKey: uploadIssueImageKey, ...uploadIssueImageOptions } =
useUploadIssueImageOptions();

useEffectOnce(() => {
queryClient.setMutationDefaults(createIssueMutationKey!, createIssueMutationOptions);
queryClient.setMutationDefaults(uploadIssueImageKey!, uploadIssueImageOptions);
});
// App.ts
<PersistQueryClientProvider
client={queryClient}
persistOptions={{ persister }}
onSuccess={() => {
setTimeout(() => queryClient.resumePausedMutations(), 0);
}}
>
<AppContent />
</PersistQueryClientProvider>

// AppContent.tsx
const { mutationKey: createIssueMutationKey, ...createIssueMutationOptions } =
useCreateIssueOptions();
const { mutationKey: uploadIssueImageKey, ...uploadIssueImageOptions } =
useUploadIssueImageOptions();

useEffectOnce(() => {
queryClient.setMutationDefaults(createIssueMutationKey!, createIssueMutationOptions);
queryClient.setMutationDefaults(uploadIssueImageKey!, uploadIssueImageOptions);
});
setTimeout is always yellow flag to me, but for now it was the only way we get this working.

Did you find this page helpful?