React Query Select Inference
Asked Answered
T

1

7

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?

Toole answered 12/3, 2023 at 12:0 Comment(1)
select? means (roughly speaking) something|undefined which always extends undefined. This is all that can be known at compile timeOligocene
F
19

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

Fidel answered 20/3, 2023 at 9:42 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.