'How to solve 'T' could be instantiated with an arbitrary type which could be unrelated to 'T | S' (ts2345)
I am trying to implement generic mutation function using ReactQuery, the code works fine but I am getting a type error which I could not solve. Can someone help me to understand what exactly TS wants from me?
Type error:
Argument of type '(oldData: T | undefined) => T | S' is not assignable to parameter of type 'Updater<T | undefined, T>'. Type '(oldData: T | undefined) => T | S' is not assignable to type 'DataUpdateFunction<T | undefined, T>'.
Type 'T | S' is not assignable to type 'T'.
'T' could be instantiated with an arbitrary type which could be unrelated to 'T | S'.ts(2345)
--
const useGenericMutation = <T, S>(
func: (data: S) => Promise<AxiosResponse<S>>,
url: string,
params?: object,
updater?: ((oldData: T, newData: S) => T) | undefined
) => {
const queryClient = useQueryClient();
return useMutation<AxiosResponse, AxiosError, 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) : data;
});
return previousData;
},
onError: (err, _, context) => {
queryClient.setQueryData([url!, params], context);
},
onSettled: () => {
queryClient.invalidateQueries([url!, params]);
},
});};
On this line:
queryClient.setQueryData<T>([url!, params], (oldData) => {
return updater ? updater(oldData!, data) : data;
});
Solution 1:[1]
T and S have no relation. The problem is that the updater function from setQueryData returns T | S, but it should only return T:
queryClient.setQueryData<T>([url!, params], (oldData) => {
return updater ? updater(oldData!, data) : data;
});
if updater is undefined, you just return data, which is of type S.
since you already get previousData with getQueryData, you can just not call setQueryData and also don't use the updater function:
const previousData = queryClient.getQueryData<T>([url!, params]);
if (updater && previousData) {
queryClient.setQueryData<T>([url!, params], updater(previousData, data))
}
Here is a TypeScript playground with that solution.
In React Query v4, it will also be allowed to return undefined from the functional updater of setQueryData, which indicates that you'd like to bail out of the update. So in that case, if you have no updater, you can do:
queryClient.setQueryData<T>([url!, params], (oldData) => {
return updater ? updater(oldData!, data) : undefined;
});
which should be equivalent to my solution calling setQueryData conditionally.
If your intention was to really just put data into the cache if there is no updater function passed in, then you need to convince the compiler that the data returned from func (which is of type S) is the same as the data expected in the query cache (which is of type T).
Defining S as: <T, S extends T> works, but it means you can't do arbitrary data transformations.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | TkDodo |
