-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Description
Describe the bug
While the arguments and return value of useQuery can clearly be typed and even inferred via generics, useQueries offers no way to provide types that extend the default type - meaning what would normally be generic in useQuery is unknown in useQueries, and thus cannot be typed correctly (without an unsafe cast).
To illustrate more clearly:
This is the signature of useQuery
https://github.com/tannerlinsley/react-query/blob/ead2e5dd5237f3d004b66316b5f36af718286d2d/src/react/useQuery.ts#L9-L15
Notice how the generic TQueryFnData is used in both the type of the options and the result. Thus, if your options contain a queryFn that returns a Promise<string>, TypeScript knows via inference that the return value will (when settled successfully) have a data property that is a string.
Contrast this to the signature of useQueries
https://github.com/tannerlinsley/react-query/blob/ead2e5dd5237f3d004b66316b5f36af718286d2d/src/react/useQueries.ts#L8
There is no api to provide info about the types that these individual queries will be dealing with. UseQueryOptions has generics, as we can see from useQuery, but without any way to provide them, they all default to unknown. This also goes for UseQueryResult; its generics also default to unknown, meaning the data property of each settled successful result is typed as unknown.
To Reproduce
Steps to reproduce the behavior:
As this is a compile-time issue, I've provided a minimal example of the issue:
import { useQueries, UseQueryOptions, UseQueryResult } from 'react-query';
function stringQueryor(): Promise<string> {
return Promise.resolve("hello there!");
}
// While this compiles correctly, the return type of this function is
// UseQueryResult<unknown, unknown, unknown>[]
// Even though the queryFns are explicitly typed to strings,
// the data property of each result (when settled successfully)
// is unknown
function inferenceOnlyUseQueries() {
return useQueries([
{ queryKey: "stringOne", queryFn: stringQueryor },
{ queryKey: "stringTwo", queryFn: stringQueryor }
]);
}
// This does NOT compile.
// TypeScript reports the error below:
//
// Type 'QueryObserverResult<unknown, unknown>[]' is not assignable to type 'QueryObserverResult<string, unknown>[]'.
// Type 'QueryObserverResult<unknown, unknown>' is not assignable to type 'QueryObserverResult<string, unknown>'.
// Type 'QueryObserverIdleResult<unknown, unknown>' is not assignable to type 'QueryObserverResult<string, unknown>'.
// Type 'QueryObserverIdleResult<unknown, unknown>' is not assignable to type 'QueryObserverIdleResult<string, unknown>'.
// Type 'unknown' is not assignable to type 'string'.
//
// Ultimately, it seems that the inference is not able to
// support the covariance of UseQueryResult<string>[] to UseQueryResult[]
function explicitReturnValue(): UseQueryResult<string> {
return useQueries([
{ queryKey: "stringOne", queryFn: stringQueryor },
{ queryKey: "stringTwo", queryFn: stringQueryor }
]);
}
// This also does NOT compile. Not in the declaration of optionOne and optionTwo,
// but in their usage as an argument to useQueries. TypeScript reports this error:
//
// Type 'UseQueryOptions<string, unknown, string>' is not assignable to type 'UseQueryOptions<unknown, unknown, unknown>'.
// Types of property 'onSuccess' are incompatible.
// Type '((data: string) => void) | undefined' is not assignable to type '((data: unknown) => void) | undefined'.
// Type '(data: string) => void' is not assignable to type '(data: unknown) => void'.
// Types of parameters 'data' and 'data' are incompatible.
// Type 'unknown' is not assignable to type 'string'.
//
// Here, it seems useQuery expects a UseQueryOptions array with generics
// of unknown. Anything more specifi (in our case, string) does not compile.
function explicitOptionValue() {
let optionOne: UseQueryOptions<string> = {
queryKey: "stringOne", queryFn: stringQueryor
};
let optionTwo: UseQueryOptions<string> = {
queryKey: "stringTwo", queryFn: stringQueryor
};
return useQueries([optionOne, optionTwo]);
}
// This also does NOT compile. The error here is noted at the return value.
// TypeScript reports this error:
//
// Type 'QueryObserverResult<unknown, unknown>[]' is not assignable to type 'QueryObserverResult<string, unknown>[]'.
//
// Same fundamental issue as before. No matter how explicit you are
// (or permissive, as we see from the first example), it is impossible to
// type the return value of useQueries so that it locks in with your option
// argument (as specified by the queryFn)
function explicitOptionAndReturnValue(): UseQueryResult<string> {
let optionOne: UseQueryOptions<string> = {
queryKey: "stringOne", queryFn: stringQueryor
};
let optionTwo: UseQueryOptions<string> = {
queryKey: "stringTwo", queryFn: stringQueryor
};
return useQueries([optionOne, optionTwo]);
}The only way I was able to get a successful compile with the correct types was with an unsafe cast:
import { useQueries, UseQueryResult } from 'react-query';
function stringQueryor(): Promise<string> {
return Promise.resolve("hello there!");
}
// This compiles, but the cast following the return value is UNSAFE
// I could cast to any type I want, since I'm casting from unknown
// I must manually provide that type, and keep that context in my
// head if and when a refactor changes the type.
function inferenceOnlyUseQueries() {
return useQueries([
{ queryKey: "stringOne", queryFn: stringQueryor },
{ queryKey: "stringTwo", queryFn: stringQueryor }
]) as UseQueryResult<string>[];
}Expected behavior
Best case scenario: the type of the return value of useQueries is inferred via the queryFn, without needing to explicitly type the options argument
import { useQueries } from 'react-query';
function stringQueryor(): Promise<string> {
return Promise.resolve("hello there!");
}
// the return type of this function is UseQueryResult<string, unknown, string>[]
function inferenceOnlyUseQueries() {
return useQueries([
{ queryKey: "stringOne", queryFn: stringQueryor },
{ queryKey: "stringTwo", queryFn: stringQueryor }
]);
}Workable scenario: the type of the return value of useQueries is inferred via the the explicit type of the options argument, without needing an explicit unsafe cast
import { useQueries, UseQueryOptions } from 'react-query';
function stringQueryor(): Promise<string> {
return Promise.resolve("hello there!");
}
// the return type of this function is UseQueryResult<string, unknown, string>[]
function inferenceOnlyUseQueries() {
let options: UseQueryOptions<string> = [
{ queryKey: "stringOne", queryFn: stringQueryor },
{ queryKey: "stringTwo", queryFn: stringQueryor }
];
return useQueries(options);
}I suspect providing generics to useQueries may be a part of the solution, but as those are not currently part of the api, i did not provide an example as "expected" here.
Screenshots
N/A
Desktop (please complete the following information):
- OS: OSX
- Browser: N/A
- Version: react-query@3.5.16 typescript@4.0.3
Smartphone (please complete the following information):
N/A
Additional context
N/A