React query mutation does now trigger again with same input

Viewed 484

When I try to use the useMutation hook for a POST API call, it works the first time with correct response (success or error). If it is an error though, if I retry the mutation with the same data as before, nothing happens. It resends the POST request if I use different data though.

Here's my use case: I've a file input. User uploads file, file is sent through a POST API using useMutation, error occurs. Now, if I, as a user, try to reupload the same file, nothing happens. But if reupload with a different file, API is sent again.

Here's my input code:

    const [newAvatarFile, setNewAvatarFile] = useState<File>(null);
    const { isSuccess, error, isLoading, mutate } = useMutation(updateUserAvatar);

    useEffect(() => {
      newAvatarFile && mutate(newAvatarFile);
    }, [newAvatarFile, mutate]);

    return (
      <input
        type="file"
        accept={'image/*'}
        onChange={(e: ChangeEvent<HTMLInputElement>) => {
          setNewAvatarFile(e.target.files[0]);
        }}
      /> 
    )

Here's my API Call function updateUserAvatar:

    import { AxiosError, AxiosResponse } from 'axios';
    import { Axios } from '../axios-instance';

    export const updateUserAvatar = async (data: File): Promise<Record<string, string>> => {
      let result: AxiosResponse;

      try {
        result = await Axios.post(
          '/some/api/url',
           { profile_picture: data },
           {
             headers: {
               'Content-Type': 'multipart/form-data',
           },
        }
      );
    } catch (err) {
      const error = err as AxiosError;
      throw new Error(
        error.response ? error.response.data.message || error.response.data : error.message
      );
    }

    return result.data;
  };

1 Answers

I can only guess that the effect is not running because the file is considered the same? Can you alternatively just run the mutation in the onChange handler:

    const [newAvatarFile, setNewAvatarFile] = useState<File>(null);
    const { isSuccess, error, isLoading, mutate } = useMutation(updateUserAvatar);

    return (
      <input
        type="file"
        accept={'image/*'}
        onChange={(e: ChangeEvent<HTMLInputElement>) => {
          setNewAvatarFile(e.target.files[0]);
          mutate(e.target.files[0])
        }}
      /> 
    )

I think this is way more explicit, and maybe you can even get rid of the newAvatarFile state if you only needed it for the effect, leaving you with:

    const { isSuccess, error, isLoading, mutate } = useMutation(updateUserAvatar);

    return (
      <input
        type="file"
        accept={'image/*'}
        onChange={(e: ChangeEvent<HTMLInputElement>) => {
          mutate(e.target.files[0])
        }}
      /> 
    )
Related