I am trying to write a custom useQuery hook to fetch a list of exercises. The hook should also accept an optional select function. In the case where the optional select function is provided, the type of the data that the useQuery hook returns should be of type number. When the select function is not provided, the type of should be PaginatedExerciseInfo<Exercises>.
I am having trouble implementing this in a way that doesn't upset typescript. Currently, when I call the hook without passing a select function to it (shown below), the data it returns is always of type number.
const { data: exercises, isError, isLoading, refetch } = useGetExercises<Exercise>(muscleGroup);
As I haven't passed a select to it, I was hoping in this case exercises would be of type PaginatedExerciseInfo<Exercises> | undefined
So far, I have tried to write the hook like this
export const getExercises = async (muscleGroup: MuscleGroup, page: number, perPage: number) => {
const { data } = await axios.get(`http://localhost:8000/exercises/${muscleGroup}?page=${page}&per_page=${perPage}&sort=-date`);
return data;
};
export const useGetExercises = <Exercises extends string>(muscleGroup: MuscleGroup, select?: (data: PaginatedExerciseInfo<Exercises>) => number) => {
const { page, perPage } = useContext(PaginationContext);
return useQuery<
PaginatedExerciseInfo<Exercises>,
unknown,
typeof select extends undefined ? PaginatedExerciseInfo<Exercises> : number
>({
queryKey: [muscleGroup, 'exercises', page, perPage],
queryFn: () => getExercises(muscleGroup, page, perPage),
select: select
});
};
Hovering over the useQuery function, I can see that the conditional type I've written for the third generic passed into useQuery is always interpreted as undefined.
Is there a way to achieve what I want to achieve using typescript?
To make select work in TypeScript, you mainly need:
TData to infer the return typeI've simplified your types a bit, so assuming that getExercises returns Promise<Exercises>, we can do:
export const useGetExercises = <TData = Exercises>(muscleGroup: MuscleGroup, select?: (data: Exercises) => TData) => {
return useQuery({
queryKey: [muscleGroup, 'exercises'],
queryFn: () => getExercises(muscleGroup),
select: select
});
};
now TData will be Exercises if we pass no select, and it will be whatever select returns if we pass it.
TypeScript playground
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With