How to use react-query useMutation with a multi-part file upload?

Viewed 3312

I'm trying to use react-query with an Ionic React webapp on a Drupal backend. I have a React form (using react-hook-form) that lets the user optionally attach an image to their user account.

Ionic interacts with Drupal via JSON:API, and Drupal's JSON:API requires that the file be uploaded first, and then the file be attached to the user account, so I need two mutations.

My question is: how can I implement the two mutations so that the file is uploaded, and then, if it was successful, I update the user account using the second mutation, and if the upload was not successful, I show an error?

Currently my code looks like this:

AddPhoto.tsx

  const onSubmit = async (userData: JsonFormUser) => {
    if (userData.photoType === 'other') {
      const myJsonData : JsonDataUserPhotoOther = {
        data: {
          id: userObject.id,
          type: 'user',
          attributes: {
            photoType: userData.photoType,
            photoName: userData.photoName,
          },
        },
      };

      // Upload the new photo.
      if (imageSrc && !imageSrc.includes(baseUrl)) {
        const myCroppedImgBlob = await getCroppedImg(
          imageSrc,
          croppedAreaPixels,
        );
        mutatePhotoFile.mutate(myCroppedImgBlob);
        // Todo: Find a way to run this only if the mutate is successful.
        mutateUser.mutate(myJsonData);

So what I want to do is run mutateUser if mutatePhotoFile is successful.

Here's mutatePhotoFile:

  const mutatePhotoFile = useMutation(
    (
      myImageBlob: string,
    ) => postFileBlobWithUserAuth(
      `${postUserUrl(userObject.id)}/field_ref_image`, myImageBlob,
    ), {
      onSuccess: (response: ResponseMutatePhoto) => {
        userDispatch({ type: 'setPhoto', payload: response });
      },
      onError: () => {
        setShowFileAlert(true);
      },
    },
  );

I tried to call mutateUser within the onSuccess of mutatePhotoFile, but I didn't have access to the myJsonData variable because that is scoped to the onSubmit function.

So then I tried using useState to store myJsonData, but mutatePhotoFile finishes resolving before the useState updates.

I am guessing there is probably a pattern for handling this in Javascript that I just don't know.

To summarize,

  1. The user clicks a submit button.
  2. I fire one react-query mutation that uploads the file to Drupal.
  3. If there is an error, I show it to the user. If there is no error, I immediately fire another mutation that updates the user account.

What kind of pattern can I use to implement this?

1 Answers

You can use the onSuccess callback that is also available on the mutate function so that you have access to myJsonData:

mutatePhotoFile.mutate(
  myCroppedImgBlob, {
    onSuccess: () => mutateUser.mutate(myJsonData)
  }
)

alternatively, you can use mutateAsync to chain the promises, but you'd need to manually catch errors (because react-query only does that for mutate - you'll get an unhandled promise rejection otherwise):

try {
  await mutatePhotoFile.mutateAsync()
  mutateUser.mutateAsync(myJsonData);
}
catch(e) {...}
Related