import { fireEvent, waitFor } from '@testing-library/react'
import * as React from 'react'
import { ErrorBoundary } from 'react-error-boundary'
import * as QueriesObserverModule from '../../../query-core/src/queriesObserver'
import {
createQueryClient,
expectType,
expectTypeNotAny,
queryKey,
renderWithClient,
sleep,
} from './utils'
import type {
QueryClient,
QueryFunction,
QueryKey,
QueryObserverResult,
UseQueryOptions,
UseQueryResult,
} from '..'
import { QueriesObserver, QueryCache, useQueries } from '..'
import type { QueryFunctionContext } from '@tanstack/query-core'
describe('useQueries', () => {
const queryCache = new QueryCache()
const queryClient = createQueryClient({ queryCache })
it('should return the correct states', async () => {
const key1 = queryKey()
const key2 = queryKey()
const results: UseQueryResult[][] = []
function Page() {
const result = useQueries({
queries: [
{
queryKey: key1,
queryFn: async () => {
await sleep(10)
return 1
},
},
{
queryKey: key2,
queryFn: async () => {
await sleep(200)
return 2
},
},
],
})
results.push(result)
return (
data1: {String(result[0].data ?? 'null')}, data2:{' '}
{String(result[1].data ?? 'null')}
)
}
const rendered = renderWithClient(queryClient, )
await waitFor(() => rendered.getByText('data1: 1, data2: 2'))
expect(results.length).toBe(3)
expect(results[0]).toMatchObject([{ data: undefined }, { data: undefined }])
expect(results[1]).toMatchObject([{ data: 1 }, { data: undefined }])
expect(results[2]).toMatchObject([{ data: 1 }, { data: 2 }])
})
it('should keep previous data if amount of queries is the same', async () => {
const key1 = queryKey()
const key2 = queryKey()
const states: UseQueryResult[][] = []
function Page() {
const [count, setCount] = React.useState(1)
const result = useQueries({
queries: [
{
queryKey: [key1, count],
keepPreviousData: true,
queryFn: async () => {
await sleep(10)
return count * 2
},
},
{
queryKey: [key2, count],
keepPreviousData: true,
queryFn: async () => {
await sleep(35)
return count * 5
},
},
],
})
states.push(result)
const isFetching = result.some((r) => r.isFetching)
return (
data1: {String(result[0].data ?? 'null')}, data2:{' '}
{String(result[1].data ?? 'null')}
isFetching: {String(isFetching)}
)
}
const rendered = renderWithClient(queryClient, )
await waitFor(() => rendered.getByText('data1: 2, data2: 5'))
fireEvent.click(rendered.getByRole('button', { name: /inc/i }))
await waitFor(() => rendered.getByText('data1: 4, data2: 10'))
await waitFor(() => rendered.getByText('isFetching: false'))
expect(states[states.length - 1]).toMatchObject([
{ status: 'success', data: 4, isPreviousData: false, isFetching: false },
{ status: 'success', data: 10, isPreviousData: false, isFetching: false },
])
})
it('should keep previous data for variable amounts of useQueries', async () => {
const key = queryKey()
const states: UseQueryResult[][] = []
function Page() {
const [count, setCount] = React.useState(2)
const result = useQueries({
queries: Array.from({ length: count }, (_, i) => ({
queryKey: [key, count, i + 1],
keepPreviousData: true,
queryFn: async () => {
await sleep(35 * (i + 1))
return (i + 1) * count * 2
},
})),
})
states.push(result)
const isFetching = result.some((r) => r.isFetching)
return (
data: {result.map((it) => it.data).join(',')}
isFetching: {String(isFetching)}
)
}
const rendered = renderWithClient(queryClient, )
await waitFor(() => rendered.getByText('data: 4,8'))
fireEvent.click(rendered.getByRole('button', { name: /inc/i }))
await waitFor(() => rendered.getByText('data: 6,12,18'))
await waitFor(() => rendered.getByText('isFetching: false'))
expect(states[states.length - 1]).toMatchObject([
{ status: 'success', data: 6, isPreviousData: false, isFetching: false },
{ status: 'success', data: 12, isPreviousData: false, isFetching: false },
{ status: 'success', data: 18, isPreviousData: false, isFetching: false },
])
})
it('should keep previous data when switching between queries', async () => {
const key = queryKey()
const states: UseQueryResult[][] = []
function Page() {
const [series1, setSeries1] = React.useState(1)
const [series2, setSeries2] = React.useState(2)
const ids = [series1, series2]
const result = useQueries({
queries: ids.map((id) => {
return {
queryKey: [key, id],
queryFn: async () => {
await sleep(5)
return id * 5
},
keepPreviousData: true,
}
}),
})
states.push(result)
const isFetching = result.some((r) => r.isFetching)
return (
data1: {String(result[0]?.data ?? 'null')}, data2:{' '}
{String(result[1]?.data ?? 'null')}
isFetching: {String(isFetching)}
)
}
const rendered = renderWithClient(queryClient, )
await waitFor(() => rendered.getByText('data1: 5, data2: 10'))
fireEvent.click(rendered.getByRole('button', { name: /setSeries2/i }))
await waitFor(() => rendered.getByText('data1: 5, data2: 15'))
fireEvent.click(rendered.getByRole('button', { name: /setSeries1/i }))
await waitFor(() => rendered.getByText('data1: 10, data2: 15'))
await waitFor(() => rendered.getByText('isFetching: false'))
expect(states[states.length - 1]).toMatchObject([
{ status: 'success', data: 10, isPreviousData: false, isFetching: false },
{ status: 'success', data: 15, isPreviousData: false, isFetching: false },
])
})
it('should not go to infinite render loop with previous data when toggling queries', async () => {
const key = queryKey()
const states: UseQueryResult[][] = []
function Page() {
const [enableId1, setEnableId1] = React.useState(true)
const ids = enableId1 ? [1, 2] : [2]
const result = useQueries({
queries: ids.map((id) => {
return {
queryKey: [key, id],
queryFn: async () => {
await sleep(5)
return id * 5
},
keepPreviousData: true,
}
}),
})
states.push(result)
const isFetching = result.some((r) => r.isFetching)
return (
data1: {String(result[0]?.data ?? 'null')}, data2:{' '}
{String(result[1]?.data ?? 'null')}
isFetching: {String(isFetching)}
)
}
const rendered = renderWithClient(queryClient, )
await waitFor(() => rendered.getByText('data1: 5, data2: 10'))
fireEvent.click(rendered.getByRole('button', { name: /set1Disabled/i }))
await waitFor(() => rendered.getByText('data1: 10, data2: null'))
await waitFor(() => rendered.getByText('isFetching: false'))
fireEvent.click(rendered.getByRole('button', { name: /set2Enabled/i }))
await waitFor(() => rendered.getByText('data1: 5, data2: 10'))
await waitFor(() => rendered.getByText('isFetching: false'))
await waitFor(() => expect(states.length).toBe(6))
expect(states[0]).toMatchObject([
{
status: 'loading',
data: undefined,
isPreviousData: false,
isFetching: true,
},
{
status: 'loading',
data: undefined,
isPreviousData: false,
isFetching: true,
},
])
expect(states[1]).toMatchObject([
{ status: 'success', data: 5, isPreviousData: false, isFetching: false },
{ status: 'success', data: 10, isPreviousData: false, isFetching: false },
])
expect(states[2]).toMatchObject([
{ status: 'success', data: 10, isPreviousData: false, isFetching: false },
])
expect(states[3]).toMatchObject([
{ status: 'success', data: 5, isPreviousData: false, isFetching: true },
{ status: 'success', data: 10, isPreviousData: false, isFetching: false },
])
expect(states[4]).toMatchObject([
{ status: 'success', data: 5, isPreviousData: false, isFetching: true },
{ status: 'success', data: 10, isPreviousData: false, isFetching: false },
])
expect(states[5]).toMatchObject([
{ status: 'success', data: 5, isPreviousData: false, isFetching: false },
{ status: 'success', data: 10, isPreviousData: false, isFetching: false },
])
})
it('handles type parameter - tuple of tuples', async () => {
const key1 = queryKey()
const key2 = queryKey()
const key3 = queryKey()
// @ts-expect-error (Page component is not rendered)
// eslint-disable-next-line
function Page() {
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])
expectType(result1[0].data)
expectType(result1[1].data)
expectType(result1[2].data)
expectType(result1[2].error)
// TData (3rd element) takes precedence over TQueryFnData (1st element)
const result2 = useQueries<
[[string, unknown, string], [string, unknown, number]]
>({
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]]>({
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]]>({
queries: [
{
queryKey: key1,
queryFn: () => 'string',
// @ts-expect-error (invalidField)
someInvalidField: [],
},
],
})
}
})
it('handles type parameter - tuple of objects', async () => {
const key1 = queryKey()
const key2 = queryKey()
const key3 = queryKey()
// @ts-expect-error (Page component is not rendered)
// eslint-disable-next-line
function Page() {
const result1 = useQueries<
[
{ queryFnData: number },
{ queryFnData: string },
{ queryFnData: string[]; error: boolean },
]
>({
queries: [
{
queryKey: key1,
queryFn: () => 1,
},
{
queryKey: key2,
queryFn: () => 'string',
},
{
queryKey: key3,
queryFn: () => ['string[]'],
},
],
})
expectType>(result1[0])
expectType>(result1[1])
expectType>(result1[2])
expectType(result1[0].data)
expectType(result1[1].data)
expectType(result1[2].data)
expectType(result1[2].error)
// TData (data prop) takes precedence over TQueryFnData (queryFnData prop)
const result2 = useQueries<
[
{ queryFnData: string; data: string },
{ queryFnData: string; data: number },
]
>({
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 }]>({
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)
expectType(result3[1].data)
// types should be enforced
useQueries<
[
{ queryFnData: string; data: string },
{ queryFnData: string; data: number; error: boolean },
]
>({
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 }]>({
queries: [
{
queryKey: key1,
queryFn: () => 'string',
// @ts-expect-error (invalidField)
someInvalidField: [],
},
],
})
}
})
it('handles array literal without type parameter to infer result type', async () => {
const key1 = queryKey()
const key2 = queryKey()
const key3 = queryKey()
const key4 = queryKey()
// @ts-expect-error (Page component is not rendered)
// eslint-disable-next-line
function Page() {
// Array.map preserves TQueryFnData
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({
queries: Array(50).map((_, i) => ({
queryKey: ['key', i] as const,
queryFn: () => i + 10,
select: (data: number) => data.toString(),
})),
})
expectType[]>(result2)
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])
expectType(result3[0].data)
expectType(result3[1].data)
// select takes precedence over queryFn
expectType(result3[2].data)
// initialData/placeholderData are enforced
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({
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({
// @ts-expect-error (onSuccess only accepts string)
queries: Array(50).map((_, i) => ({
queryKey: ['key', i] as const,
queryFn: () => i + 10,
select: (data: number) => data.toString(),
onSuccess: (_data: number) => null,
})),
})
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({
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])
// handles when queryFn returns a Promise
const result5 = useQueries({
queries: [
{
queryKey: key1,
queryFn: () => Promise.resolve('string'),
onSuccess: (a: string) => {
expectType(a)
expectTypeNotAny(a)
},
// @ts-expect-error (refuses to accept a Promise)
onSettled: (a: Promise) => null,
},
],
})
expectType>(result5[0])
// Array as const does not throw error
const result6 = useQueries({
queries: [
{
queryKey: ['key1'],
queryFn: () => 'string',
},
{
queryKey: ['key1'],
queryFn: () => 123,
},
],
} as const)
expectType>(result6[0])
expectType>(result6[1])
// field names should be enforced - array literal
useQueries({
queries: [
{
queryKey: key1,
queryFn: () => 'string',
// @ts-expect-error (invalidField)
someInvalidField: [],
},
],
})
// field names should be enforced - Array.map() result
useQueries({
// @ts-expect-error (invalidField)
queries: Array(10).map(() => ({
someInvalidField: '',
})),
})
// field names should be enforced - array literal
useQueries({
queries: [
{
queryKey: key1,
queryFn: () => 'string',
// @ts-expect-error (invalidField)
someInvalidField: [],
},
],
})
// supports queryFn using fetch() to return Promise - Array.map() result
useQueries({
queries: Array(50).map((_, i) => ({
queryKey: ['key', i] as const,
queryFn: () =>
fetch('return Promise').then((resp) => resp.json()),
})),
})
// supports queryFn using fetch() to return Promise - array literal
useQueries({
queries: [
{
queryKey: key1,
queryFn: () =>
fetch('return Promise').then((resp) => resp.json()),
},
],
})
}
})
it('handles strongly typed queryFn factories and useQueries wrappers', () => {
// QueryKey + queryFn factory
type QueryKeyA = ['queryA']
const getQueryKeyA = (): QueryKeyA => ['queryA']
type GetQueryFunctionA = () => QueryFunction
const getQueryFunctionA: GetQueryFunctionA = () => async () => {
return 1
}
type SelectorA = (data: number) => [number, string]
const getSelectorA = (): SelectorA => (data) => [data, data.toString()]
type QueryKeyB = ['queryB', string]
const getQueryKeyB = (id: string): QueryKeyB => ['queryB', id]
type GetQueryFunctionB = () => QueryFunction
const getQueryFunctionB: GetQueryFunctionB = () => async () => {
return '1'
}
type SelectorB = (data: string) => [string, number]
const getSelectorB = (): SelectorB => (data) => [data, +data]
// Wrapper with strongly typed array-parameter
function useWrappedQueries<
TQueryFnData,
TError,
TData,
TQueryKey extends QueryKey,
>(queries: UseQueryOptions[]) {
return useQueries({
queries: queries.map(
// no need to type the mapped query
(query) => {
const { queryFn: fn, queryKey: key, onError: err } = query
expectType | undefined>(fn)
return {
queryKey: key,
onError: err,
queryFn: fn
? (ctx: QueryFunctionContext) => {
expectType(ctx.queryKey)
return fn.call({}, ctx)
}
: undefined,
}
},
),
})
}
// @ts-expect-error (Page component is not rendered)
// eslint-disable-next-line
function Page() {
const result = useQueries({
queries: [
{
queryKey: getQueryKeyA(),
queryFn: getQueryFunctionA(),
},
{
queryKey: getQueryKeyB('id'),
queryFn: getQueryFunctionB(),
},
],
})
expectType>(result[0])
expectType>(result[1])
const withSelector = useQueries({
queries: [
{
queryKey: getQueryKeyA(),
queryFn: getQueryFunctionA(),
select: getSelectorA(),
},
{
queryKey: getQueryKeyB('id'),
queryFn: getQueryFunctionB(),
select: getSelectorB(),
},
],
})
expectType>(
withSelector[0],
)
expectType>(
withSelector[1],
)
const withWrappedQueries = useWrappedQueries(
Array(10).map(() => ({
queryKey: getQueryKeyA(),
queryFn: getQueryFunctionA(),
select: getSelectorA(),
})),
)
expectType[]>(
withWrappedQueries,
)
}
})
it('should not change state if unmounted', async () => {
const key1 = queryKey()
// We have to mock the QueriesObserver to not unsubscribe
// the listener when the component is unmounted
class QueriesObserverMock extends QueriesObserver {
subscribe(listener: any) {
super.subscribe(listener)
return () => void 0
}
}
const QueriesObserverSpy = jest
.spyOn(QueriesObserverModule, 'QueriesObserver')
.mockImplementation((fn) => {
return new QueriesObserverMock(fn)
})
function Queries() {
useQueries({
queries: [
{
queryKey: key1,
queryFn: async () => {
await sleep(10)
return 1
},
},
],
})
return (
queries
)
}
function Page() {
const [mounted, setMounted] = React.useState(true)
return (
{mounted && }
)
}
const { getByText } = renderWithClient(queryClient, )
fireEvent.click(getByText('unmount'))
// Should not display the console error
// "Warning: Can't perform a React state update on an unmounted component"
await sleep(20)
QueriesObserverSpy.mockRestore()
})
describe('with custom context', () => {
it('should return the correct states', async () => {
const context = React.createContext(undefined)
const key1 = queryKey()
const key2 = queryKey()
const results: UseQueryResult[][] = []
function Page() {
const result = useQueries({
context,
queries: [
{
queryKey: key1,
queryFn: async () => {
await sleep(5)
return 1
},
},
{
queryKey: key2,
queryFn: async () => {
await sleep(10)
return 2
},
},
],
})
results.push(result)
return (
data1: {result[0].data}
data2: {result[1].data}
)
}
const rendered = renderWithClient(queryClient, , { context })
await waitFor(() => {
rendered.getByText('data1: 1')
rendered.getByText('data2: 2')
})
expect(results[0]).toMatchObject([
{ data: undefined },
{ data: undefined },
])
expect(results[results.length - 1]).toMatchObject([
{ data: 1 },
{ data: 2 },
])
})
it('should throw if the context is necessary and is not passed to useQueries', async () => {
const context = React.createContext(undefined)
const key1 = queryKey()
const key2 = queryKey()
const results: UseQueryResult[][] = []
function Page() {
const result = useQueries({
queries: [
{
queryKey: key1,
queryFn: async () => 1,
},
{
queryKey: key2,
queryFn: async () => 2,
},
],
})
results.push(result)
return null
}
const rendered = renderWithClient(
queryClient,
error boundary
}>
,
{ context },
)
await waitFor(() => rendered.getByText('error boundary'))
})
})
it("should throw error if in one of queries' queryFn throws and useErrorBoundary is in use", async () => {
const key1 = queryKey()
const key2 = queryKey()
const key3 = queryKey()
const key4 = queryKey()
function Page() {
useQueries({
queries: [
{
queryKey: key1,
queryFn: () =>
Promise.reject(
new Error(
'this should not throw because useErrorBoundary is not set',
),
),
},
{
queryKey: key2,
queryFn: () => Promise.reject(new Error('single query error')),
useErrorBoundary: true,
retry: false,
},
{
queryKey: key3,
queryFn: async () => 2,
},
{
queryKey: key4,
queryFn: async () =>
Promise.reject(
new Error('this should not throw because query#2 already did'),
),
useErrorBoundary: true,
retry: false,
},
],
})
return null
}
const rendered = renderWithClient(
queryClient,
(
error boundary
{error.message}
)}
>
,
)
await waitFor(() => rendered.getByText('error boundary'))
await waitFor(() => rendered.getByText('single query error'))
})
it("should throw error if in one of queries' queryFn throws and useErrorBoundary function resolves to true", async () => {
const key1 = queryKey()
const key2 = queryKey()
const key3 = queryKey()
const key4 = queryKey()
function Page() {
useQueries({
queries: [
{
queryKey: key1,
queryFn: () =>
Promise.reject(
new Error(
'this should not throw because useErrorBoundary function resolves to false',
),
),
useErrorBoundary: () => false,
retry: false,
},
{
queryKey: key2,
queryFn: async () => 2,
},
{
queryKey: key3,
queryFn: () => Promise.reject(new Error('single query error')),
useErrorBoundary: () => true,
retry: false,
},
{
queryKey: key4,
queryFn: async () =>
Promise.reject(
new Error('this should not throw because query#3 already did'),
),
useErrorBoundary: true,
retry: false,
},
],
})
return null
}
const rendered = renderWithClient(
queryClient,
(
error boundary
{error.message}
)}
>
,
)
await waitFor(() => rendered.getByText('error boundary'))
await waitFor(() => rendered.getByText('single query error'))
})
})