From 744ad659673c0d467892b70048b4acc14320d1f0 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Fri, 17 Mar 2023 19:39:49 +0100 Subject: [PATCH 1/4] feat: queryOptions helper function so that we can define and share options outside of useQuery, but still get the type inference --- .../src/__tests__/useQuery.types.test.tsx | 22 ++++++++++++++++++- packages/react-query/src/index.ts | 2 +- packages/react-query/src/useQuery.ts | 22 +++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/packages/react-query/src/__tests__/useQuery.types.test.tsx b/packages/react-query/src/__tests__/useQuery.types.test.tsx index 14d1e21c27..2e38c64836 100644 --- a/packages/react-query/src/__tests__/useQuery.types.test.tsx +++ b/packages/react-query/src/__tests__/useQuery.types.test.tsx @@ -1,4 +1,4 @@ -import { useQuery } from '../useQuery' +import { queryOptions, useQuery } from '../useQuery' import type { Expect, Equal } from './utils' import { doNotExecute } from './utils' @@ -23,6 +23,26 @@ describe('initialData', () => { }) }) + it('TData should be defined when passed through queryOptions', () => { + doNotExecute(() => { + const options = queryOptions({ + queryKey: ['key'], + queryFn: () => { + return { + wow: true, + } + }, + initialData: { + wow: true, + }, + }) + const { data } = useQuery(options) + + const result: Expect> = true + return result + }) + }) + it('TData should always be defined when initialData is provided as a function which ALWAYS returns the data', () => { doNotExecute(() => { const { data } = useQuery({ diff --git a/packages/react-query/src/index.ts b/packages/react-query/src/index.ts index b4e0910344..684e37e89e 100644 --- a/packages/react-query/src/index.ts +++ b/packages/react-query/src/index.ts @@ -7,7 +7,7 @@ export * from '@tanstack/query-core' export * from './types' export { useQueries } from './useQueries' export type { QueriesResults, QueriesOptions } from './useQueries' -export { useQuery } from './useQuery' +export { useQuery, queryOptions } from './useQuery' export { QueryClientContext, QueryClientProvider, diff --git a/packages/react-query/src/useQuery.ts b/packages/react-query/src/useQuery.ts index 8c55fd1ec9..e1df5e718d 100644 --- a/packages/react-query/src/useQuery.ts +++ b/packages/react-query/src/useQuery.ts @@ -7,6 +7,28 @@ import type { } from './types' import { useBaseQuery } from './useBaseQuery' +export function queryOptions< + TQueryFnData = unknown, + TError = unknown, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +>( + options: UndefinedInitialDataOptions, +): UndefinedInitialDataOptions + +export function queryOptions< + TQueryFnData = unknown, + TError = unknown, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +>( + options: DefinedInitialDataOptions, +): DefinedInitialDataOptions + +export function queryOptions(options: unknown) { + return options +} + // HOOK type UndefinedInitialDataOptions< TQueryFnData = unknown, From 7b250eb6ce45d1ce8ff32a71d78d3c3462edbf35 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Fri, 17 Mar 2023 20:02:35 +0100 Subject: [PATCH 2/4] docs: queryOptions --- docs/react/typescript.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/react/typescript.md b/docs/react/typescript.md index 10752d6fe4..c2394e2c0c 100644 --- a/docs/react/typescript.md +++ b/docs/react/typescript.md @@ -123,6 +123,20 @@ if (axios.isAxiosError(error)) { [typescript playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgVwM4FMCKz1QJ5wC+cAZlBCHAOQACMAhgHaoMDGA1gPRTr2swBaAI458VALAAoUJFhx6AD2ARUpcpSqLlqCZKkw8YdHADi5ZGDgBeRHGAATAFxxGyEACNcRKVNYRm8CToMKwAFmYQFqo2ABQAlM4ACurAGAA8ERYA2gC6AHzWBVoqAHQA5sExVJxl5mA6cSUwoeiMMTyokMzGVgUdXRgl9vQMcT6SfgG2uORQRNYoGNi4eDFIIisA0uh4zllUtZH1VDkANHAb+ABijM5BIeF1qoRjkpyccJ9fAHoA-OPAEhwGLFVAlVIAQSUKgAolBZjEZtA4nFEFJPkioOi4O84H8pIQgA) [//]: # 'Playground6' +A neat trick that also works is specifying an empty `onError` handler just to get type inference for the error field: + +```tsx +import axios, { AxiosError } from 'axios' + +const { error } = useQuery({ + queryKey: ['groups'], + queryFn: fetchGroups, + onError: (_error: AxiosError) => {}, +}) +error +// ^? const error: AxiosError | null +``` + ### Registering a global Error TanStack Query v5 allows for a way to set a global Error type for everything, without having to specify generics on call-sides, by amending the `Register` interface. This will make sure inference still works, but the error field will be of the specified type: @@ -140,6 +154,25 @@ const { error } = useQuery({ queryKey: ['groups'], queryFn: fetchGroups }) [//]: # 'Materials' +## Typing Query Options + +If you inline query options into `useQuery`, you'll get automatic type inference. However, you might want to extract the query options into a separate function to share them between `useQuery` and e.g. `prefetchQuery`. In that case, you'd lose type inference. To get it back, you can use `queryOptions` helper: + +```ts +import { queryOptions } from '@tanstack/react-query' + +function groupOptions() { + return queryOptions({ + queryKey: ['groups'], + queryFn: fetchGroups, + staleTime: 5 * 1000, + }) +} + +useQuery(groupOptions()) +queryClient.prefetchQuery(groupOptions()) +``` + ## Further Reading For tips and tricks around type inference, have a look at [React Query and TypeScript](../community/tkdodos-blog#6-react-query-and-typescript) from From 4276b1002ae1e811be4cd9fa2ebedbe93b534510 Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Sun, 30 Apr 2023 18:29:02 +0200 Subject: [PATCH 3/4] docs: remove reference to onError --- docs/react/typescript.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/docs/react/typescript.md b/docs/react/typescript.md index f20b81dc73..ae3a865901 100644 --- a/docs/react/typescript.md +++ b/docs/react/typescript.md @@ -123,20 +123,6 @@ if (axios.isAxiosError(error)) { [typescript playground](https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgVwM4FMCKz1QJ5wC+cAZlBCHAOQACMAhgHaoMDGA1gPRTr2swBaAI458VALAAoUJFhx6AD2ARUpcpSqLlqCZKkw8YdHADi5ZGDgBeRHGAATAFxxGyEACNcRKVNYRm8CToMKwAFmYQFqo2ABQAlM4ACurAGAA8ERYA2gC6AHzWBVoqAHQA5sExVJxl5mA6cSUwoeiMMTyokMzGVgUdXRgl9vQMcT6SfgG2uORQRNYoGNi4eDFIIisA0uh4zllUtZH1VDkANHAb+ABijM5BIeF1qoRjkpyccJ9fAHoA-OPAEhwGLFVAlVIAQSUKgAolBZjEZtA4nFEFJPkioOi4O84H8pIQgA) [//]: # 'Playground6' -A neat trick that also works is specifying an empty `onError` handler just to get type inference for the error field: - -```tsx -import axios, { AxiosError } from 'axios' - -const { error } = useQuery({ - queryKey: ['groups'], - queryFn: fetchGroups, - onError: (_error: AxiosError) => {}, -}) -error -// ^? const error: AxiosError | null -``` - ### Registering a global Error TanStack Query v5 allows for a way to set a global Error type for everything, without having to specify generics on call-sides, by amending the `Register` interface. This will make sure inference still works, but the error field will be of the specified type: From 07d6aa921c6c75458b21def05cee3dcd4af8331d Mon Sep 17 00:00:00 2001 From: Dominik Dorfmeister Date: Sun, 30 Apr 2023 18:31:02 +0200 Subject: [PATCH 4/4] docs: update migration guide --- docs/react/guides/migrating-to-v5.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/react/guides/migrating-to-v5.md b/docs/react/guides/migrating-to-v5.md index e16f15e9eb..7a51885c13 100644 --- a/docs/react/guides/migrating-to-v5.md +++ b/docs/react/guides/migrating-to-v5.md @@ -439,6 +439,10 @@ You can adjust the `maxPages` value according to the UX and refetching performan Note that the infinite list must be bi-directional, which requires both `getNextPageParam` and `getPreviousPageParam` to be defined. +### Typesafe way to create Query Options + +See the [Typescript Docs](../typescript#typing-query-options) for more details. + ### CreateStore We are now exposing a way to customize how queries are stored internally. Per default, a `Map` is used but, with the new `createStore` function, you can now use any data structure you want.