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.