react-query icon indicating copy to clipboard operation
react-query copied to clipboard

Type issue with useGenericMutation function.

Open berkerdemirer opened this issue 2 years ago • 3 comments

Hello,

I am getting the following type issue within useGenericMutation function. Can you help me to understand what is needed to be done?

image

Thanks for the great tutorial.

berkerdemirer avatar Apr 09 '22 13:04 berkerdemirer

Hi! Thank you, glad it was useful.

About this issue: it's strange that I didn't notice it before. I think the most logical way to fix it - change the types to this:

const useGenericMutation = <T, S>(
  func: (data: T | S) => Promise<AxiosResponse<S>>,
  url: string,
  params?: object,
  updater?: ((oldData: T, newData: S) => T) | undefined
) => {
  const queryClient = useQueryClient();

  return useMutation<AxiosResponse, AxiosError, T | S>(func, {
    onMutate: async (data) => {
      await queryClient.cancelQueries([url!, params]);

      const previousData = queryClient.getQueryData([url!, params]);

      queryClient.setQueryData<T>([url!, params], (oldData) => {
        return updater ? updater(oldData!, data as S) : (data as T);
      });

      return previousData;
    },
    onError: (err, _, context) => {
      queryClient.setQueryData([url!, params], context);
    },
    onSettled: () => {
      queryClient.invalidateQueries([url!, params]);
    },
  });
};

The issue happened, because if there is no updater function we should set (queryClient.setQueryData) the data, which we received from the response. In our case we define data which we send as S interface, and T interface we define the data which we already have (and in some cases it could be the same values, that's why we are getting this issue).

For example:

  • in case of POST request: T - JobInterface[], S - JobInterface (because existing data - the list of all jobs, the data which we want to add - just one new item of this list). The response will be T (the actual list of objects).
  • in case of DELETE request: T - JobInterface[], S - string | number (existing data - is a list, and the data we send in the request - id of deleting item). The response will be T (the actual list of objects).
  • in case of PATCH request: T and S will be the same, because the existing data (T) is JobInterface and the new data (S) we want to change is the JobInterface too. According this logic, for this case data in useMutation should be not just S, but T | S.

So, if data could be T | S, we should cast it manually. Actually if there is an updater - it means data - S, if there is no updater - usually it means that T and S - are the same, that's why we could cast it to T.

queryClient.setQueryData<T>([url!, params], (oldData) => {
  return updater ? updater(oldData!, data as S) : (data as T);
});

I know it's a bit tricky, because we have different case and one generic function, but I think it will work. Sorry if the explanation is a messy. I made a fix comment.

horprogs avatar Apr 10 '22 17:04 horprogs

Thanks for the explanation and the response! I will take my time to analyze and understand this

berkerdemirer avatar Apr 10 '22 21:04 berkerdemirer

I want to add one new record, for that how to pass old data?

codingwithashu avatar Sep 19 '23 18:09 codingwithashu