diff --git a/package-lock.json b/package-lock.json index 34b055f6a1..bd46664f0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44932,7 +44932,9 @@ "url": "https://github.com/sponsors/tannerlinsley" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-native": "*" }, "peerDependenciesMeta": { "react-dom": { diff --git a/packages/react-query-persist-client/src/__tests__/PersistQueryClientProvider.test.tsx b/packages/react-query-persist-client/src/__tests__/PersistQueryClientProvider.test.tsx index 45321ce16a..6802f60bb7 100644 --- a/packages/react-query-persist-client/src/__tests__/PersistQueryClientProvider.test.tsx +++ b/packages/react-query-persist-client/src/__tests__/PersistQueryClientProvider.test.tsx @@ -6,6 +6,7 @@ import { useQuery, UseQueryResult, useQueries, + DefinedUseQueryResult, } from '@tanstack/react-query' import { createQueryClient, @@ -200,7 +201,7 @@ describe('PersistQueryClientProvider', () => { test('should show initialData while restoring', async () => { const key = queryKey() - const states: UseQueryResult[] = [] + const states: DefinedUseQueryResult[] = [] const queryClient = createQueryClient() await queryClient.prefetchQuery(key, () => Promise.resolve('hydrated')) diff --git a/packages/react-query/src/__tests__/useQuery.test.tsx b/packages/react-query/src/__tests__/useQuery.test.tsx index 88c994eb39..37ab64707a 100644 --- a/packages/react-query/src/__tests__/useQuery.test.tsx +++ b/packages/react-query/src/__tests__/useQuery.test.tsx @@ -19,6 +19,7 @@ import { QueryFunction, QueryFunctionContext, UseQueryOptions, + DefinedUseQueryResult, } from '..' import { ErrorBoundary } from 'react-error-boundary' @@ -142,7 +143,7 @@ describe('useQuery', () => { ) => Promise, options?: Omit< UseQueryOptions, - 'queryKey' | 'queryFn' + 'queryKey' | 'queryFn' | 'initialData' >, ) => useQuery(qk, () => fetcher(qk[1], 'token'), options) const test = useWrappedQuery([''], async () => '1') @@ -159,7 +160,7 @@ describe('useQuery', () => { fetcher: () => Promise, options?: Omit< UseQueryOptions, - 'queryKey' | 'queryFn' + 'queryKey' | 'queryFn' | 'initialData' >, ) => useQuery(qk, fetcher, options) const testFuncStyle = useWrappedFuncStyleQuery([''], async () => true) @@ -1295,7 +1296,7 @@ describe('useQuery', () => { it('should use query function from hook when the existing query does not have a query function', async () => { const key = queryKey() - const results: UseQueryResult[] = [] + const results: DefinedUseQueryResult[] = [] queryClient.setQueryData(key, 'set') @@ -1712,7 +1713,7 @@ describe('useQuery', () => { it('should not show initial data from next query if keepPreviousData is set', async () => { const key = queryKey() - const states: UseQueryResult[] = [] + const states: DefinedUseQueryResult[] = [] function Page() { const [count, setCount] = React.useState(0) @@ -3025,7 +3026,7 @@ describe('useQuery', () => { it('should fetch if initial data is set', async () => { const key = queryKey() - const states: UseQueryResult[] = [] + const states: DefinedUseQueryResult[] = [] function Page() { const state = useQuery(key, () => 'data', { @@ -3055,7 +3056,7 @@ describe('useQuery', () => { it('should not fetch if initial data is set with a stale time', async () => { const key = queryKey() - const states: UseQueryResult[] = [] + const states: DefinedUseQueryResult[] = [] function Page() { const state = useQuery(key, () => 'data', { @@ -3085,7 +3086,7 @@ describe('useQuery', () => { it('should fetch if initial data updated at is older than stale time', async () => { const key = queryKey() - const states: UseQueryResult[] = [] + const states: DefinedUseQueryResult[] = [] const oneSecondAgo = Date.now() - 1000 @@ -3123,7 +3124,7 @@ describe('useQuery', () => { it('should fetch if "initial data updated at" is exactly 0', async () => { const key = queryKey() - const states: UseQueryResult[] = [] + const states: DefinedUseQueryResult[] = [] function Page() { const state = useQuery(key, () => 'data', { @@ -3154,7 +3155,7 @@ describe('useQuery', () => { it('should keep initial data when the query key changes', async () => { const key = queryKey() - const states: UseQueryResult<{ count: number }>[] = [] + const states: DefinedUseQueryResult<{ count: number }>[] = [] function Page() { const [count, setCount] = React.useState(0) @@ -3629,7 +3630,7 @@ describe('useQuery', () => { it('should mark query as fetching, when using initialData', async () => { const key = queryKey() - const results: UseQueryResult[] = [] + const results: DefinedUseQueryResult[] = [] function Page() { const result = useQuery(key, () => 'serverData', { initialData: 'data' }) @@ -3648,7 +3649,7 @@ describe('useQuery', () => { it('should initialize state properly, when initialData is falsy', async () => { const key = queryKey() - const results: UseQueryResult[] = [] + const results: DefinedUseQueryResult[] = [] function Page() { const result = useQuery(key, () => 1, { initialData: 0 }) @@ -3668,7 +3669,7 @@ describe('useQuery', () => { // // See https://github.com/tannerlinsley/react-query/issues/214 it('data should persist when enabled is changed to false', async () => { const key = queryKey() - const results: UseQueryResult[] = [] + const results: DefinedUseQueryResult[] = [] function Page() { const [shouldFetch, setShouldFetch] = React.useState(true) diff --git a/packages/react-query/src/__tests__/useQuery.types.test.tsx b/packages/react-query/src/__tests__/useQuery.types.test.tsx new file mode 100644 index 0000000000..d049b53e00 --- /dev/null +++ b/packages/react-query/src/__tests__/useQuery.types.test.tsx @@ -0,0 +1,157 @@ +import { useQuery } from '../useQuery' + +export type Equal = (() => T extends X ? 1 : 2) extends < + T, +>() => T extends Y ? 1 : 2 + ? true + : false + +export type Expect = T + +const doNotExecute = (_func: () => void) => true + +describe('initialData', () => { + describe('Config object overload', () => { + it('TData should always be defined when initialData is provided as an object', () => { + doNotExecute(() => { + const { data } = useQuery({ + queryFn: () => { + return { + wow: true, + } + }, + initialData: { + wow: true, + }, + }) + + 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({ + queryFn: () => { + return { + wow: true, + } + }, + initialData: () => ({ + wow: true, + }), + }) + + const result: Expect> = true + return result + }) + }) + + it('TData should have undefined in the union when initialData is NOT provided', () => { + doNotExecute(() => { + const { data } = useQuery({ + queryFn: () => { + return { + wow: true, + } + }, + }) + + const result: Expect> = + true + return result + }) + }) + + it('TData should have undefined in the union when initialData is provided as a function which can return undefined', () => { + doNotExecute(() => { + const { data } = useQuery({ + queryFn: () => { + return { + wow: true, + } + }, + initialData: () => undefined as { wow: boolean } | undefined, + }) + + const result: Expect> = + true + return result + }) + }) + }) + + describe('Query key overload', () => { + it('TData should always be defined when initialData is provided', () => { + doNotExecute(() => { + const { data } = useQuery(['key'], { + queryFn: () => { + return { + wow: true, + } + }, + initialData: { + wow: true, + }, + }) + + const result: Expect> = true + return result + }) + }) + + it('TData should have undefined in the union when initialData is NOT provided', () => { + doNotExecute(() => { + const { data } = useQuery(['key'], { + queryFn: () => { + return { + wow: true, + } + }, + }) + + const result: Expect> = + true + return result + }) + }) + }) + + describe('Query key and func', () => { + it('TData should always be defined when initialData is provided', () => { + doNotExecute(() => { + const { data } = useQuery( + ['key'], + () => { + return { + wow: true, + } + }, + { + initialData: { + wow: true, + }, + }, + ) + + const result: Expect> = true + return result + }) + }) + + it('TData should have undefined in the union when initialData is NOT provided', () => { + doNotExecute(() => { + const { data } = useQuery(['key'], () => { + return { + wow: true, + } + }) + + const result: Expect> = + true + return result + }) + }) + }) +}) diff --git a/packages/react-query/src/types.ts b/packages/react-query/src/types.ts index 20b8817cb1..c83bb5b26a 100644 --- a/packages/react-query/src/types.ts +++ b/packages/react-query/src/types.ts @@ -65,6 +65,11 @@ export type UseQueryResult< TError = unknown, > = UseBaseQueryResult +export type DefinedUseQueryResult = Omit< + UseQueryResult, + 'data' +> & { data: TData } + export type UseInfiniteQueryResult< TData = unknown, TError = unknown, diff --git a/packages/react-query/src/useQuery.ts b/packages/react-query/src/useQuery.ts index 10fdfebc6c..eee97a3f4b 100644 --- a/packages/react-query/src/useQuery.ts +++ b/packages/react-query/src/useQuery.ts @@ -1,10 +1,10 @@ import { - QueryObserver, + parseQueryArgs, QueryFunction, QueryKey, - parseQueryArgs, + QueryObserver, } from '@tanstack/query-core' -import { UseQueryOptions, UseQueryResult } from './types' +import { DefinedUseQueryResult, UseQueryOptions, UseQueryResult } from './types' import { useBaseQuery } from './useBaseQuery' // HOOK @@ -15,8 +15,24 @@ export function useQuery< TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, >( - options: UseQueryOptions, + options: Omit< + UseQueryOptions, + 'initialData' + > & { initialData?: () => undefined }, ): UseQueryResult + +export function useQuery< + TQueryFnData = unknown, + TError = unknown, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +>( + options: Omit< + UseQueryOptions, + 'initialData' + > & { initialData: TQueryFnData | (() => TQueryFnData) }, +): DefinedUseQueryResult + export function useQuery< TQueryFnData = unknown, TError = unknown, @@ -26,9 +42,23 @@ export function useQuery< queryKey: TQueryKey, options?: Omit< UseQueryOptions, - 'queryKey' - >, + 'queryKey' | 'initialData' + > & { initialData?: () => undefined }, ): UseQueryResult + +export function useQuery< + TQueryFnData = unknown, + TError = unknown, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +>( + queryKey: TQueryKey, + options?: Omit< + UseQueryOptions, + 'queryKey' | 'initialData' + > & { initialData: TQueryFnData | (() => TQueryFnData) }, +): DefinedUseQueryResult + export function useQuery< TQueryFnData = unknown, TError = unknown, @@ -39,9 +69,24 @@ export function useQuery< queryFn: QueryFunction, options?: Omit< UseQueryOptions, - 'queryKey' | 'queryFn' - >, + 'queryKey' | 'queryFn' | 'initialData' + > & { initialData?: () => undefined }, ): UseQueryResult + +export function useQuery< + TQueryFnData = unknown, + TError = unknown, + TData = TQueryFnData, + TQueryKey extends QueryKey = QueryKey, +>( + queryKey: TQueryKey, + queryFn: QueryFunction, + options?: Omit< + UseQueryOptions, + 'queryKey' | 'queryFn' | 'initialData' + > & { initialData: TQueryFnData | (() => TQueryFnData) }, +): DefinedUseQueryResult + export function useQuery< TQueryFnData, TError,