Home > Enterprise >  Type argument at moment of use
Type argument at moment of use

Time:05-14

I'm trying to build a generic tool for handling the response to React Query useQuery requests in my app, and build this out using TypeScript, so that the data is correctly typed.

In a parent component, the use case would be something like this:

const queryResult = useQuery<MyResponseType, Error>('whatever');

return (
  <ReactQueryLoader useQueryResult={queryResult}>
    {data => {
      // Data should be of MyResponseType type, but is unknown
      return <pre>{JSON.stringify(data, null, 2)}</pre>;
    }}
  </ReactQueryLoader>
);

I tried defining this ReactQueryLoader as so:

import React from "react";
import { UseQueryResult } from "react-query";

import { Loading } from "./Loading";

interface ReactQueryLoaderProps {
  useQueryResult: UseQueryResult<unknown, Error>;
  handleError?: (error: Error) => void;
  children: (data: unknown) => JSX.Element;
}

export const ReactQueryLoader = ({
  useQueryResult,
  handleError = error => {
    console.error(error);
    throw new Error(error.message);
  },
  children,
}: ReactQueryLoaderProps): null | JSX.Element => {
  if (useQueryResult.isError) {
    handleError(useQueryResult.error);
    return null;
  }

  if (useQueryResult.isLoading) {
    return <Loading />;
  }

  return children(useQueryResult.data);
};

But when I inspect the data passed to the child, instead of being the type of the expected response data, it is unknown. I'm having trouble getting this to work, as someone with a mid-low-level understanding of TypeScript. I thought I could apply the same pattern as in this answer to another question I had, but I can't figure out how to do it in this context.

I tried this:

interface ReactQueryLoaderProps {
  // ...
  children: <T extends unknown>(data: T) => JSX.Element;
}

But got this in the parent element:

(parameter) data: T extends unknown

I also tried:

interface ReactQueryLoaderProps {
  useQueryResult:  UseQueryResult<T extends unknown, Error>;

But that provoked two errors:

  • Under the T: Cannot find name 'T'.
  • Under the comma: '?' expected.

Is this possible to do?

CodePudding user response:

The ReactQueryLoader function needs to be generic here, as well as the props type.

That might look something like this:

interface ReactQueryLoaderProps<T extends UseQueryResult<unknown, Error>> {
  useQueryResult: T;
  handleError?: (error: Error) => void;
  children: (data: T['data']) => JSX.Element;
}

And

export function ReactQueryLoader<T extends UseQueryResult<unknown, Error>>({
  useQueryResult,
  handleError,
  children,
}: ReactQueryLoaderProps<T>): null | JSX.Element {
  //...
}

Here ReactQueryLoader is now a generic function that captures the query result type as T and passes that to the generic props type ReactQueryLoaderProps.

ReactQueryLoaderProps then uses that type as the type of useQueryResult, and pulls the ['data'] property from that type for the type of the children function parameter.

See playground

  • Related