Repository: tannerlinsley/react-query
Pull request #3493 (2022-04-10 22:54:15)

fix(types): fix promise inference in useQueries
fixes issue #3483 

This new helper `QueryFunctionData` was introduced in the latest branch:

```typescript
type QueryFunctionData<T> = T extends undefined ? never : T

type QueryFunction<
  T = unknown,
  TQueryKey extends QueryKey = QueryKey
> = (
  context: QueryFunctionContext<TQueryKey>
) => QueryFunctionData<T> | Promise<QueryFunctionData<T>>
```

Looks like when a conditional expression is duplicated like this in a union, it's not able to pass along any `infer` logic correctly.

[(TS Playground link with a simplified example - not restricted to promises though... you can replace `Promise<X>` with `Array<X>` for the same effect)](https://www.typescriptlang.org/play?#code/C4TwDgpgBAigrhATiAYnAdgY2ASwPboAiAhsMQDwAqAfFALxSVQQAewE6AJgM5QacQAZjnQROUAPxRRANyRQAXIwBQy0JCgB5OO0QBVdPnRVaDeElQZsRkmRNQAPlAAKiPAFsc3COXPI0WLgEthQ01Grg0ACS6IJIetwiAObauvYMTKzsXLyp8YYE5CJxiIx+ICG0UpTlIYrSEHKIERooxDgANvRQMSUJyXmI5K4eXj7cwIjJ1OGYBBNQgu0dShNT6EndAOTcHhDAABbJW1DEvG2dqurR6KL6BcY03eUB1sGkoY4ubp7eJuHXHqxeKJDYxO7pRjMNgcHhAu4GIxFYGlGoIZCVSRldEVD71WRIFrQZxnXgMXog5LgpDDH5jchraazebAKBgUmrSbJba7dz7I4bE5nFykoA)

This means the `useQueries` helper types are getting confused, thinking `TQueryFnData` could itself be a promise.

Interestingly, if we make it so there's only 1 instance of `QueryFunctionData`, with a union as its type argument, as I'm doing in this PR, it seems to work just fine. [Rules of TS claim that the two expressions should be equivalent](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types), so it's a bit counter-intuitive that 1 way works and the other doesn't.

So my hunch is that the issue was caused by how TS goes about evaluating... when it's doing inference on a union of two nested conditional expressions, it may be evaluating twice, and merges the results together, meaning we end up with both `string` and `Promise<string>` that fit the bill.

 (I'm asking around to get a better understanding of the mechanics - will let you know if it's caused by something else).




vercel[bot]: This pull request is being automatically deployed with Vercel ([learn more](https://vercel.link/github-learn-more)).  
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: [https://vercel.com/tanstack/react-query/5MXumaLSNDFG8ovPuEH8yEo61p5K](https://vercel.com/tanstack/react-query/5MXumaLSNDFG8ovPuEH8yEo61p5K)  
✅ Preview: *In Progress*


codesandbox[bot]: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/tannerlinsley/react-query/pr/3493/builds/238177) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 913bda0f8c43fb79e448a84ad69e7dfbb8d47a9b:

|Sandbox| Source |
|--|--|
|[tannerlinsley/react-query: basic](https://codesandbox.io/s/tannerlinsley-react-query-basic-xffmk2)| Configuration |
|[tannerlinsley/react-query: basic-typescript](https://codesandbox.io/s/tannerlinsley-react-query-basic-typescript-0jik9b)| Configuration |

codecov[bot]: # [Codecov](https://codecov.io/gh/tannerlinsley/react-query/pull/3493?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tanner+Linsley) Report
> Merging [#3493](https://codecov.io/gh/tannerlinsley/react-query/pull/3493?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tanner+Linsley) (913bda0) into [beta](https://codecov.io/gh/tannerlinsley/react-query/commit/58a9fe9cf15c6ddfb0f55fd3437387c163f60eab?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tanner+Linsley) (58a9fe9) will **decrease** coverage by `0.04%`.
> The diff coverage is `n/a`.

```diff
@@            Coverage Diff             @@
##             beta    #3493      +/-   ##
==========================================
- Coverage   96.94%   96.90%   -0.05%     
==========================================
  Files          46       46              
  Lines        2390     2390              
  Branches      714      714              
==========================================
- Hits         2317     2316       -1     
- Misses         71       72       +1     
  Partials        2        2              
```


| [Impacted Files](https://codecov.io/gh/tannerlinsley/react-query/pull/3493?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tanner+Linsley) | Coverage Δ | |
|---|---|---|
| [src/reactjs/useBaseQuery.ts](https://codecov.io/gh/tannerlinsley/react-query/pull/3493/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tanner+Linsley#diff-c3JjL3JlYWN0anMvdXNlQmFzZVF1ZXJ5LnRz) | `98.18% <0.00%> (-1.82%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/tannerlinsley/react-query/pull/3493?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tanner+Linsley).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tanner+Linsley)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/tannerlinsley/react-query/pull/3493?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tanner+Linsley). Last update [58a9fe9...913bda0](https://codecov.io/gh/tannerlinsley/react-query/pull/3493?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tanner+Linsley). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Tanner+Linsley).


Patch:
diff --git a/src/core/types.ts b/src/core/types.ts
index b3b075192..d096a7941 100644
--- a/src/core/types.ts
+++ b/src/core/types.ts
@@ -14,7 +14,7 @@ export type QueryFunction<
   TQueryKey extends QueryKey = QueryKey
 > = (
   context: QueryFunctionContext<TQueryKey>
-) => QueryFunctionData<T> | Promise<QueryFunctionData<T>>
+) => QueryFunctionData<T | Promise<T>>
 
 export interface QueryFunctionContext<
   TQueryKey extends QueryKey = QueryKey,


Test patch:
diff --git a/src/core/tests/query.test.tsx b/src/core/tests/query.test.tsx
index f53ff9ad8..22d6e166a 100644
--- a/src/core/tests/query.test.tsx
+++ b/src/core/tests/query.test.tsx
@@ -803,7 +803,8 @@ describe('query', () => {
 
     const observer = new QueryObserver(queryClient, {
       queryKey: key,
-      queryFn: (() => undefined) as any,
+      // @ts-expect-error (queryFn must not return undefined)
+      queryFn: () => undefined,
       retry: false,
     })
 
diff --git a/src/persistQueryClient/tests/PersistQueryClientProvider.test.tsx b/src/persistQueryClient/tests/PersistQueryClientProvider.test.tsx
index 7cac63915..537c51962 100644
--- a/src/persistQueryClient/tests/PersistQueryClientProvider.test.tsx
+++ b/src/persistQueryClient/tests/PersistQueryClientProvider.test.tsx
@@ -147,7 +147,7 @@ describe('PersistQueryClientProvider', () => {
 
       return (
         <div>
-          <h1>{String(state.data)}</h1>
+          <h1>{state.data}</h1>
           <h2>fetchStatus: {state.fetchStatus}</h2>
         </div>
       )
diff --git a/src/reactjs/tests/useQueries.test.tsx b/src/reactjs/tests/useQueries.test.tsx
index eccbbac05..67fc39bf0 100644
--- a/src/reactjs/tests/useQueries.test.tsx
+++ b/src/reactjs/tests/useQueries.test.tsx
@@ -776,8 +776,25 @@ describe('useQueries', () => {
       expectType<QueryObserverResult<string, unknown>>(result4[1])
       expectType<QueryObserverResult<number, unknown>>(result4[2])
 
-      // Array as const does not throw error
+      // handles when queryFn returns a Promise
       const result5 = useQueries({
+        queries: [
+          {
+            queryKey: key1,
+            queryFn: () => Promise.resolve('string'),
+            onSuccess: (a: string) => {
+              expectType<string>(a)
+              expectTypeNotAny(a)
+            },
+            // @ts-expect-error (refuses to accept a Promise)
+            onSettled: (a: Promise<string>) => null,
+          },
+        ],
+      })
+      expectType<QueryObserverResult<string, unknown>>(result5[0])
+
+      // Array as const does not throw error
+      const result6 = useQueries({
         queries: [
           {
             queryKey: ['key1'],
@@ -789,8 +806,8 @@ describe('useQueries', () => {
           },
         ],
       } as const)
-      expectType<QueryObserverResult<string, unknown>>(result5[0])
-      expectType<QueryObserverResult<number, unknown>>(result5[1])
+      expectType<QueryObserverResult<string, unknown>>(result6[0])
+      expectType<QueryObserverResult<number, unknown>>(result6[1])
 
       // field names should be enforced - array literal
       useQueries({
diff --git a/src/reactjs/tests/useQuery.test.tsx b/src/reactjs/tests/useQuery.test.tsx
index 5f2a5906b..af246055c 100644
--- a/src/reactjs/tests/useQuery.test.tsx
+++ b/src/reactjs/tests/useQuery.test.tsx
@@ -38,6 +38,10 @@ describe('useQuery', () => {
       expectType<unknown>(noQueryFn.data)
       expectType<unknown>(noQueryFn.error)
 
+      // it should not be possible for queryFn to return undefined
+      // @ts-expect-error (queryFn returns undefined)
+      useQuery(key, () => undefined)
+
       // it should infer the result type from the query function
       const fromQueryFn = useQuery(key, () => 'test')
       expectType<string | undefined>(fromQueryFn.data)