Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Query Select Inference

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?

like image 380
G.W.F Avatar asked Jun 08 '26 13:06

G.W.F


1 Answers

To make select work in TypeScript, you mainly need:

  • 1 generic TData to infer the return type
  • have that generic default to whatever the QueryFunction returns
  • leverage type inference

I'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

like image 115
TkDodo Avatar answered Jun 10 '26 06:06

TkDodo



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!