From d48f90994a6bdcace2af30a4e3d4e3a7598268ae Mon Sep 17 00:00:00 2001 From: Rene Dellefont Date: Tue, 7 Dec 2021 14:36:37 -0500 Subject: [PATCH 1/3] feat(useQueries): update API to use object syntax New v4 API - instead of taking an array of queries, `useQueries` now accepts an object with a `queries` key. The value of this key is an array of queries (this array is unchanged from v3). --- src/reactjs/useQueries.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/reactjs/useQueries.ts b/src/reactjs/useQueries.ts index 339f6a0937..8f5001e123 100644 --- a/src/reactjs/useQueries.ts +++ b/src/reactjs/useQueries.ts @@ -110,9 +110,11 @@ type QueriesResults< : // Fallback UseQueryResult[] -export function useQueries( +export function useQueries({ + queries, +}: { queries: readonly [...QueriesOptions] -): QueriesResults { +}): QueriesResults { const mountedRef = React.useRef(false) const [, forceUpdate] = React.useState(0) From 44b6a34888c4829161c3775616cc7fa2094d411e Mon Sep 17 00:00:00 2001 From: Rene Dellefont Date: Tue, 7 Dec 2021 14:36:56 -0500 Subject: [PATCH 2/3] test(useQueries): update tests for new API --- src/reactjs/tests/useQueries.test.tsx | 782 ++++++++++++++------------ 1 file changed, 409 insertions(+), 373 deletions(-) diff --git a/src/reactjs/tests/useQueries.test.tsx b/src/reactjs/tests/useQueries.test.tsx index 0b549747dc..f6d260cbbe 100644 --- a/src/reactjs/tests/useQueries.test.tsx +++ b/src/reactjs/tests/useQueries.test.tsx @@ -30,22 +30,24 @@ describe('useQueries', () => { const results: UseQueryResult[][] = [] function Page() { - const result = useQueries([ - { - queryKey: key1, - queryFn: async () => { - await sleep(5) - return 1 + const result = useQueries({ + queries: [ + { + queryKey: key1, + queryFn: async () => { + await sleep(5) + return 1 + }, }, - }, - { - queryKey: key2, - queryFn: async () => { - await sleep(10) - return 2 + { + queryKey: key2, + queryFn: async () => { + await sleep(10) + return 2 + }, }, - }, - ]) + ], + }) results.push(result) return null } @@ -67,24 +69,26 @@ describe('useQueries', () => { function Page() { const [count, setCount] = React.useState(1) - const result = useQueries([ - { - queryKey: [key1, count], - keepPreviousData: true, - queryFn: async () => { - await sleep(5) - return count * 2 + const result = useQueries({ + queries: [ + { + queryKey: [key1, count], + keepPreviousData: true, + queryFn: async () => { + await sleep(5) + return count * 2 + }, }, - }, - { - queryKey: [key2, count], - keepPreviousData: true, - queryFn: async () => { - await sleep(10) - return count * 5 + { + queryKey: [key2, count], + keepPreviousData: true, + queryFn: async () => { + await sleep(10) + return count * 5 + }, }, - }, - ]) + ], + }) states.push(result) React.useEffect(() => { @@ -151,16 +155,16 @@ describe('useQueries', () => { function Page() { const [count, setCount] = React.useState(2) - const result = useQueries( - Array.from({ length: count }, (_, i) => ({ + const result = useQueries({ + queries: Array.from({ length: count }, (_, i) => ({ queryKey: [key, count, i + 1], keepPreviousData: true, queryFn: async () => { await sleep(5 * (i + 1)) return (i + 1) * count * 2 }, - })) - ) + })), + }) states.push(result) @@ -261,8 +265,8 @@ describe('useQueries', () => { const [series2, setSeries2] = React.useState(2) const ids = [series1, series2] - const result = useQueries( - ids.map(id => { + const result = useQueries({ + queries: ids.map(id => { return { queryKey: [key, id], queryFn: async () => { @@ -271,8 +275,8 @@ describe('useQueries', () => { }, keepPreviousData: true, } - }) - ) + }), + }) states.push(result) @@ -356,8 +360,8 @@ describe('useQueries', () => { const [enableId1, setEnableId1] = React.useState(true) const ids = enableId1 ? [1, 2] : [2] - const result = useQueries( - ids.map(id => { + const result = useQueries({ + queries: ids.map(id => { return { queryKey: [key, id], queryFn: async () => { @@ -366,8 +370,8 @@ describe('useQueries', () => { }, keepPreviousData: true, } - }) - ) + }), + }) states.push(result) @@ -443,20 +447,22 @@ describe('useQueries', () => { // @ts-expect-error (Page component is not rendered) // eslint-disable-next-line function Page() { - const result1 = useQueries<[[number], [string], [string[], boolean]]>([ - { - queryKey: key1, - queryFn: () => 1, - }, - { - queryKey: key2, - queryFn: () => 'string', - }, - { - queryKey: key3, - queryFn: () => ['string[]'], - }, - ]) + const result1 = useQueries<[[number], [string], [string[], boolean]]>({ + queries: [ + { + queryKey: key1, + queryFn: () => 1, + }, + { + queryKey: key2, + queryFn: () => 'string', + }, + { + queryKey: key3, + queryFn: () => ['string[]'], + }, + ], + }) expectType>(result1[0]) expectType>(result1[1]) expectType>(result1[2]) @@ -468,80 +474,86 @@ describe('useQueries', () => { // TData (3rd element) takes precedence over TQueryFnData (1st element) const result2 = useQueries< [[string, unknown, string], [string, unknown, number]] - >([ - { - queryKey: key1, - queryFn: () => 'string', - select: a => { - expectType(a) - expectTypeNotAny(a) - return a.toLowerCase() - }, - }, - { - queryKey: key2, - queryFn: () => 'string', - select: a => { - expectType(a) - expectTypeNotAny(a) - return parseInt(a) - }, - }, - ]) + >({ + queries: [ + { + queryKey: key1, + queryFn: () => 'string', + select: a => { + expectType(a) + expectTypeNotAny(a) + return a.toLowerCase() + }, + }, + { + queryKey: key2, + queryFn: () => 'string', + select: a => { + expectType(a) + expectTypeNotAny(a) + return parseInt(a) + }, + }, + ], + }) expectType>(result2[0]) expectType>(result2[1]) expectType(result2[0].data) expectType(result2[1].data) // types should be enforced - useQueries<[[string, unknown, string], [string, boolean, number]]>([ - { - queryKey: key1, - queryFn: () => 'string', - select: a => { - expectType(a) - expectTypeNotAny(a) - return a.toLowerCase() - }, - onSuccess: a => { - expectType(a) - expectTypeNotAny(a) - }, - placeholderData: 'string', - // @ts-expect-error (initialData: string) - initialData: 123, - }, - { - queryKey: key2, - queryFn: () => 'string', - select: a => { - expectType(a) - expectTypeNotAny(a) - return parseInt(a) - }, - onSuccess: a => { - expectType(a) - expectTypeNotAny(a) - }, - onError: e => { - expectType(e) - expectTypeNotAny(e) - }, - placeholderData: 'string', - // @ts-expect-error (initialData: string) - initialData: 123, - }, - ]) + useQueries<[[string, unknown, string], [string, boolean, number]]>({ + queries: [ + { + queryKey: key1, + queryFn: () => 'string', + select: a => { + expectType(a) + expectTypeNotAny(a) + return a.toLowerCase() + }, + onSuccess: a => { + expectType(a) + expectTypeNotAny(a) + }, + placeholderData: 'string', + // @ts-expect-error (initialData: string) + initialData: 123, + }, + { + queryKey: key2, + queryFn: () => 'string', + select: a => { + expectType(a) + expectTypeNotAny(a) + return parseInt(a) + }, + onSuccess: a => { + expectType(a) + expectTypeNotAny(a) + }, + onError: e => { + expectType(e) + expectTypeNotAny(e) + }, + placeholderData: 'string', + // @ts-expect-error (initialData: string) + initialData: 123, + }, + ], + }) // field names should be enforced - useQueries<[[string]]>([ - { - queryKey: key1, - queryFn: () => 'string', - // @ts-expect-error (invalidField) - someInvalidField: [], - }, - ]) + useQueries<[[string]]>({ + queries: [ + { + queryKey: key1, + queryFn: () => 'string', + // @ts-expect-error (invalidField) + someInvalidField: [], + }, + ], + }) } }) @@ -559,20 +571,22 @@ describe('useQueries', () => { { queryFnData: string }, { queryFnData: string[]; error: boolean } ] - >([ - { - queryKey: key1, - queryFn: () => 1, - }, - { - queryKey: key2, - queryFn: () => 'string', - }, - { - queryKey: key3, - queryFn: () => ['string[]'], - }, - ]) + >({ + queries: [ + { + queryKey: key1, + queryFn: () => 1, + }, + { + queryKey: key2, + queryFn: () => 'string', + }, + { + queryKey: key3, + queryFn: () => ['string[]'], + }, + ], + }) expectType>(result1[0]) expectType>(result1[1]) expectType>(result1[2]) @@ -587,52 +601,56 @@ describe('useQueries', () => { { queryFnData: string; data: string }, { queryFnData: string; data: number } ] - >([ - { - queryKey: key1, - queryFn: () => 'string', - select: a => { - expectType(a) - expectTypeNotAny(a) - return a.toLowerCase() - }, - }, - { - queryKey: key2, - queryFn: () => 'string', - select: a => { - expectType(a) - expectTypeNotAny(a) - return parseInt(a) - }, - }, - ]) + >({ + queries: [ + { + queryKey: key1, + queryFn: () => 'string', + select: a => { + expectType(a) + expectTypeNotAny(a) + return a.toLowerCase() + }, + }, + { + queryKey: key2, + queryFn: () => 'string', + select: a => { + expectType(a) + expectTypeNotAny(a) + return parseInt(a) + }, + }, + ], + }) expectType>(result2[0]) expectType>(result2[1]) expectType(result2[0].data) expectType(result2[1].data) // can pass only TData (data prop) although TQueryFnData will be left unknown - const result3 = useQueries<[{ data: string }, { data: number }]>([ - { - queryKey: key1, - queryFn: () => 'string', - select: a => { - expectType(a) - expectTypeNotAny(a) - return a as string - }, - }, - { - queryKey: key2, - queryFn: () => 'string', - select: a => { - expectType(a) - expectTypeNotAny(a) - return a as number - }, - }, - ]) + const result3 = useQueries<[{ data: string }, { data: number }]>({ + queries: [ + { + queryKey: key1, + queryFn: () => 'string', + select: a => { + expectType(a) + expectTypeNotAny(a) + return a as string + }, + }, + { + queryKey: key2, + queryFn: () => 'string', + select: a => { + expectType(a) + expectTypeNotAny(a) + return a as number + }, + }, + ], + }) expectType>(result3[0]) expectType>(result3[1]) expectType(result3[0].data) @@ -644,54 +662,58 @@ describe('useQueries', () => { { queryFnData: string; data: string }, { queryFnData: string; data: number; error: boolean } ] - >([ - { - queryKey: key1, - queryFn: () => 'string', - select: a => { - expectType(a) - expectTypeNotAny(a) - return a.toLowerCase() - }, - onSuccess: a => { - expectType(a) - expectTypeNotAny(a) - }, - placeholderData: 'string', - // @ts-expect-error (initialData: string) - initialData: 123, - }, - { - queryKey: key2, - queryFn: () => 'string', - select: a => { - expectType(a) - expectTypeNotAny(a) - return parseInt(a) - }, - onSuccess: a => { - expectType(a) - expectTypeNotAny(a) - }, - onError: e => { - expectType(e) - expectTypeNotAny(e) - }, - placeholderData: 'string', - // @ts-expect-error (initialData: string) - initialData: 123, - }, - ]) + >({ + queries: [ + { + queryKey: key1, + queryFn: () => 'string', + select: a => { + expectType(a) + expectTypeNotAny(a) + return a.toLowerCase() + }, + onSuccess: a => { + expectType(a) + expectTypeNotAny(a) + }, + placeholderData: 'string', + // @ts-expect-error (initialData: string) + initialData: 123, + }, + { + queryKey: key2, + queryFn: () => 'string', + select: a => { + expectType(a) + expectTypeNotAny(a) + return parseInt(a) + }, + onSuccess: a => { + expectType(a) + expectTypeNotAny(a) + }, + onError: e => { + expectType(e) + expectTypeNotAny(e) + }, + placeholderData: 'string', + // @ts-expect-error (initialData: string) + initialData: 123, + }, + ], + }) // field names should be enforced - useQueries<[{ queryFnData: string }]>([ - { - queryKey: key1, - queryFn: () => 'string', - // @ts-expect-error (invalidField) - someInvalidField: [], - }, - ]) + useQueries<[{ queryFnData: string }]>({ + queries: [ + { + queryKey: key1, + queryFn: () => 'string', + // @ts-expect-error (invalidField) + someInvalidField: [], + }, + ], + }) } }) @@ -705,40 +727,42 @@ describe('useQueries', () => { // eslint-disable-next-line function Page() { // Array.map preserves TQueryFnData - const result1 = useQueries( - Array(50).map((_, i) => ({ + const result1 = useQueries({ + queries: Array(50).map((_, i) => ({ queryKey: ['key', i] as const, queryFn: () => i + 10, - })) - ) + })), + }) expectType[]>(result1) expectType(result1[0]?.data) // Array.map preserves TData - const result2 = useQueries( - Array(50).map((_, i) => ({ + const result2 = useQueries({ + queries: Array(50).map((_, i) => ({ queryKey: ['key', i] as const, queryFn: () => i + 10, select: (data: number) => data.toString(), - })) - ) + })), + }) expectType[]>(result2) - const result3 = useQueries([ - { - queryKey: key1, - queryFn: () => 1, - }, - { - queryKey: key2, - queryFn: () => 'string', - }, - { - queryKey: key3, - queryFn: () => ['string[]'], - select: () => 123, - }, - ]) + const result3 = useQueries({ + queries: [ + { + queryKey: key1, + queryFn: () => 1, + }, + { + queryKey: key2, + queryFn: () => 'string', + }, + { + queryKey: key3, + queryFn: () => ['string[]'], + select: () => 123, + }, + ], + }) expectType>(result3[0]) expectType>(result3[1]) expectType>(result3[2]) @@ -748,154 +772,164 @@ describe('useQueries', () => { expectType(result3[2].data) // initialData/placeholderData are enforced - useQueries([ - { - queryKey: key1, - queryFn: () => 'string', - placeholderData: 'string', - // @ts-expect-error (initialData: string) - initialData: 123, - }, - { - queryKey: key2, - queryFn: () => 123, - // @ts-expect-error (placeholderData: number) - placeholderData: 'string', - initialData: 123, - }, - ]) + useQueries({ + queries: [ + { + queryKey: key1, + queryFn: () => 'string', + placeholderData: 'string', + // @ts-expect-error (initialData: string) + initialData: 123, + }, + { + queryKey: key2, + queryFn: () => 123, + // @ts-expect-error (placeholderData: number) + placeholderData: 'string', + initialData: 123, + }, + ], + }) // select / onSuccess / onSettled params are "indirectly" enforced - useQueries([ - // unfortunately TS will not suggest the type for you - { - queryKey: key1, - queryFn: () => 'string', - // @ts-expect-error (noImplicitAny) - onSuccess: a => null, - // @ts-expect-error (noImplicitAny) - onSettled: a => null, - }, - // however you can add a type to the callback - { - queryKey: key2, - queryFn: () => 'string', - onSuccess: (a: string) => { - expectType(a) - expectTypeNotAny(a) - }, - onSettled: (a: string | undefined) => { - expectType(a) - expectTypeNotAny(a) - }, - }, - // the type you do pass is enforced - { - queryKey: key3, - queryFn: () => 'string', - // @ts-expect-error (only accepts string) - onSuccess: (a: number) => null, - }, - { - queryKey: key4, - queryFn: () => 'string', - select: (a: string) => parseInt(a), - // @ts-expect-error (select is defined => only accepts number) - onSuccess: (a: string) => null, - onSettled: (a: number | undefined) => { - expectType(a) - expectTypeNotAny(a) - }, - }, - ]) + useQueries({ + queries: [ + // unfortunately TS will not suggest the type for you + { + queryKey: key1, + queryFn: () => 'string', + // @ts-expect-error (noImplicitAny) + onSuccess: a => null, + // @ts-expect-error (noImplicitAny) + onSettled: a => null, + }, + // however you can add a type to the callback + { + queryKey: key2, + queryFn: () => 'string', + onSuccess: (a: string) => { + expectType(a) + expectTypeNotAny(a) + }, + onSettled: (a: string | undefined) => { + expectType(a) + expectTypeNotAny(a) + }, + }, + // the type you do pass is enforced + { + queryKey: key3, + queryFn: () => 'string', + // @ts-expect-error (only accepts string) + onSuccess: (a: number) => null, + }, + { + queryKey: key4, + queryFn: () => 'string', + select: (a: string) => parseInt(a), + // @ts-expect-error (select is defined => only accepts number) + onSuccess: (a: string) => null, + onSettled: (a: number | undefined) => { + expectType(a) + expectTypeNotAny(a) + }, + }, + ], + }) // callbacks are also indirectly enforced with Array.map - useQueries( + useQueries({ // @ts-expect-error (onSuccess only accepts string) - Array(50).map((_, i) => ({ + queries: Array(50).map((_, i) => ({ queryKey: ['key', i] as const, queryFn: () => i + 10, select: (data: number) => data.toString(), onSuccess: (_data: number) => null, - })) - ) - useQueries( - Array(50).map((_, i) => ({ + })), + }) + useQueries({ + queries: Array(50).map((_, i) => ({ queryKey: ['key', i] as const, queryFn: () => i + 10, select: (data: number) => data.toString(), onSuccess: (_data: string) => null, - })) - ) + })), + }) // results inference works when all the handlers are defined - const result4 = useQueries([ - { - queryKey: key1, - queryFn: () => 'string', - // @ts-expect-error (noImplicitAny) - onSuccess: a => null, - // @ts-expect-error (noImplicitAny) - onSettled: a => null, - }, - { - queryKey: key2, - queryFn: () => 'string', - onSuccess: (a: string) => { - expectType(a) - expectTypeNotAny(a) - }, - onSettled: (a: string | undefined) => { - expectType(a) - expectTypeNotAny(a) - }, - }, - { - queryKey: key4, - queryFn: () => 'string', - select: (a: string) => parseInt(a), - onSuccess: (_a: number) => null, - onSettled: (a: number | undefined) => { - expectType(a) - expectTypeNotAny(a) - }, - }, - ]) + const result4 = useQueries({ + queries: [ + { + queryKey: key1, + queryFn: () => 'string', + // @ts-expect-error (noImplicitAny) + onSuccess: a => null, + // @ts-expect-error (noImplicitAny) + onSettled: a => null, + }, + { + queryKey: key2, + queryFn: () => 'string', + onSuccess: (a: string) => { + expectType(a) + expectTypeNotAny(a) + }, + onSettled: (a: string | undefined) => { + expectType(a) + expectTypeNotAny(a) + }, + }, + { + queryKey: key4, + queryFn: () => 'string', + select: (a: string) => parseInt(a), + onSuccess: (_a: number) => null, + onSettled: (a: number | undefined) => { + expectType(a) + expectTypeNotAny(a) + }, + }, + ], + }) expectType>(result4[0]) expectType>(result4[1]) expectType>(result4[2]) // Array as const does not throw error - const result5 = useQueries([ - { - queryKey: ['key1'], - queryFn: () => 'string', - }, - { - queryKey: ['key1'], - queryFn: () => 123, - }, - ] as const) + const result5 = useQueries({ + queries: [ + { + queryKey: ['key1'], + queryFn: () => 'string', + }, + { + queryKey: ['key1'], + queryFn: () => 123, + }, + ], + } as const) expectType>(result5[0]) expectType>(result5[1]) // field names should be enforced - array literal - useQueries([ - { - queryKey: key1, - queryFn: () => 'string', - // @ts-expect-error (invalidField) - someInvalidField: [], - }, - ]) + useQueries({ + queries: [ + { + queryKey: key1, + queryFn: () => 'string', + // @ts-expect-error (invalidField) + someInvalidField: [], + }, + ], + }) // field names should be enforced - Array.map() result - useQueries( + useQueries({ // @ts-expect-error (invalidField) - Array(10).map(() => ({ + queries: Array(10).map(() => ({ someInvalidField: '', - })) - ) + })), + }) } }) @@ -918,15 +952,17 @@ describe('useQueries', () => { }) function Queries() { - useQueries([ - { - queryKey: key1, - queryFn: async () => { - await sleep(10) - return 1 + useQueries({ + queries: [ + { + queryKey: key1, + queryFn: async () => { + await sleep(10) + return 1 + }, }, - }, - ]) + ], + }) return (
From 757e1ff5f65d63f2f854ee764aa84312077b49cf Mon Sep 17 00:00:00 2001 From: Rene Dellefont Date: Tue, 7 Dec 2021 23:43:57 -0500 Subject: [PATCH 3/3] docs(useQueries): update docs for v4 API --- docs/src/pages/guides/migrating-to-react-query-4.md | 10 ++++++++++ docs/src/pages/guides/parallel-queries.md | 8 ++++---- docs/src/pages/reference/useQueries.md | 12 +++++++----- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/docs/src/pages/guides/migrating-to-react-query-4.md b/docs/src/pages/guides/migrating-to-react-query-4.md index 14002bec89..d8ee9beb50 100644 --- a/docs/src/pages/guides/migrating-to-react-query-4.md +++ b/docs/src/pages/guides/migrating-to-react-query-4.md @@ -207,6 +207,16 @@ new QueryClient({ }) ``` +### new API for `useQueries` + +The `useQueries` hook now accepts an object with a `queries` prop as its input. The value of the `queries` prop is an array of queries (this array is identical to what was passed into `useQueries` in v3). + +```diff +- useQueries([{ queryKey1, queryFn1, options1 }, { queryKey2, queryFn2, options2 }]) ++ useQueries({ queries: [{ queryKey1, queryFn1, options1 }, { queryKey2, queryFn2, options2 }] }) +``` + + ### Removed undocumented methods from the `queryClient` The methods `cancelMutatations` and `executeMutation` were undocumented and unused internally, so we removed them. Since they were just wrappers around methods available on the `mutationCache`, you can still use the functionality. diff --git a/docs/src/pages/guides/parallel-queries.md b/docs/src/pages/guides/parallel-queries.md index 79dac5ce82..74b20a7015 100644 --- a/docs/src/pages/guides/parallel-queries.md +++ b/docs/src/pages/guides/parallel-queries.md @@ -25,17 +25,17 @@ function App () { If the number of queries you need to execute is changing from render to render, you cannot use manual querying since that would violate the rules of hooks. Instead, React Query provides a `useQueries` hook, which you can use to dynamically execute as many queries in parallel as you'd like. -`useQueries` accepts an **array of query options objects** and returns an **array of query results**: +`useQueries` accepts an **options object** with a **queries key** whose value is an **array of query objects**. It returns an **array of query results**: ```js function App({ users }) { - const userQueries = useQueries( - users.map(user => { + const userQueries = useQueries({ + queries: users.map(user => { return { queryKey: ['user', user.id], queryFn: () => fetchUserById(user.id), } }) - ) + }) } ``` diff --git a/docs/src/pages/reference/useQueries.md b/docs/src/pages/reference/useQueries.md index f69e5fe3af..0849033ef2 100644 --- a/docs/src/pages/reference/useQueries.md +++ b/docs/src/pages/reference/useQueries.md @@ -6,15 +6,17 @@ title: useQueries The `useQueries` hook can be used to fetch a variable number of queries: ```js -const results = useQueries([ - { queryKey: ['post', 1], queryFn: fetchPost }, - { queryKey: ['post', 2], queryFn: fetchPost }, -]) +const results = useQueries({ + queries: [ + { queryKey: ['post', 1], queryFn: fetchPost }, + { queryKey: ['post', 2], queryFn: fetchPost } + ] +}) ``` **Options** -The `useQueries` hook accepts an array with query option objects identical to the [`useQuery` hook](/reference/useQuery). +The `useQueries` hook accepts an options object with a **queries** key whose value is an array with query option objects identical to the [`useQuery` hook](/reference/useQuery). **Returns**