diff --git a/infinite/index.ts b/infinite/index.ts index 3b99169d7..6528d63f8 100644 --- a/infinite/index.ts +++ b/infinite/index.ts @@ -57,7 +57,8 @@ export const infinite = ((useSWRNext: SWRHook) => revalidateAll = false, persistSize = false, revalidateFirstPage = true, - revalidateOnMount = false + revalidateOnMount = false, + parallel = false } = config // The serialized key of the first page. This key will be used to store @@ -138,9 +139,13 @@ export const infinite = ((useSWRNext: SWRHook) => const pageSize = resolvePageSize() + const revalidators = [] + let previousPageData = null for (let i = 0; i < pageSize; ++i) { - const [pageKey, pageArg] = serialize(getKey(i, previousPageData)) + const [pageKey, pageArg] = serialize( + getKey(i, parallel ? null : previousPageData) + ) if (!pageKey) { // `pageKey` is falsy, stop fetching new pages. @@ -173,11 +178,27 @@ export const infinite = ((useSWRNext: SWRHook) => !config.compare(originalData[i], pageData)) if (fn && shouldFetchPage) { - pageData = await fn(pageArg) - setSWRCache({ data: pageData, _k: pageArg }) + const revalidate = async () => { + pageData = await fn(pageArg) + setSWRCache({ data: pageData, _k: pageArg }) + data[i] = pageData + } + if (parallel) { + revalidators.push(revalidate) + } else { + await revalidate() + } + } else { + data[i] = pageData + } + if (!parallel) { + previousPageData = pageData } - data.push(pageData) - previousPageData = pageData + } + + // flush all revalidateions in parallel + if (parallel) { + await Promise.all(revalidators.map(r => r())) } // once we executed the data fetching based on the context, clear the context diff --git a/infinite/types.ts b/infinite/types.ts index 704d079cb..bdfea9845 100644 --- a/infinite/types.ts +++ b/infinite/types.ts @@ -32,6 +32,7 @@ export interface SWRInfiniteConfiguration< revalidateAll?: boolean persistSize?: boolean revalidateFirstPage?: boolean + parallel?: boolean fetcher?: Fn } diff --git a/test/use-swr-infinite.test.tsx b/test/use-swr-infinite.test.tsx index 4867f8760..b70ac9d82 100644 --- a/test/use-swr-infinite.test.tsx +++ b/test/use-swr-infinite.test.tsx @@ -1692,4 +1692,153 @@ describe('useSWRInfinite', () => { await screen.findByText(`size: 2`) await screen.findByText(`swr: ${key}-2,`) }) + + it('should support the parallel option', async () => { + // mock api + const pageData = ['apple', 'banana', 'pineapple'] + + const key = createKey() + function Page() { + const { data } = useSWRInfinite( + index => [key, index], + ([_, index]) => createResponse(`${pageData[index]}, `, { delay: 50 }), + { + initialSize: 3, + parallel: true + } + ) + + return
data:{data}
+ } + + renderWithConfig() + screen.getByText('data:') + + // If SWR sends requests sequentially, it takes 150ms at least + await act(() => sleep(100)) + screen.getByText('data:apple, banana, pineapple,') + }) + + it('should return the first error happened in parallel requests', async () => { + // mock api + const pageData = [ + { data: new Error('apple'), delay: 50 }, + { data: new Error('banana'), delay: 30 }, + { data: 'pineapple', delay: 10 } + ] + + const key = createKey() + function Page() { + const { data, error } = useSWRInfinite( + index => [key, index], + ([_, index]) => + createResponse(pageData[index].data as string, { + delay: pageData[index].delay + }), + { + initialSize: 3, + parallel: true + } + ) + + if (error) { + return
error:{error.message}
+ } + + return
data:{data}
+ } + + renderWithConfig() + screen.getByText('data:') + + await act(() => sleep(50)) + screen.getByText('error:banana') + }) + + it('should send request sequentially when the parallel option is disabled', async () => { + // mock api + const pageData = ['apple', 'banana', 'pineapple'] + + const key = createKey() + function Page() { + const { data } = useSWRInfinite( + index => [key, index], + ([_, index]) => createResponse(`${pageData[index]}, `, { delay: 50 }), + { + initialSize: 3, + parallel: false + } + ) + + return
data:{data}
+ } + + renderWithConfig() + screen.getByText('data:') + + // If SWR sends requests sequentially, it takes 150ms at least + await act(() => sleep(100)) + screen.getByText('data:') + await act(() => sleep(200)) + screen.getByText('data:apple, banana, pineapple,') + }) + + it('should be the parallel option false by default', async () => { + // mock api + const pageData = ['apple', 'banana', 'pineapple'] + + const key = createKey() + function Page() { + const { data } = useSWRInfinite( + index => [key, index], + ([_, index]) => createResponse(`${pageData[index]}, `, { delay: 50 }), + { + initialSize: 3 + } + ) + + return
data:{data}
+ } + + renderWithConfig() + screen.getByText('data:') + + // If SWR sends requests sequentially, it takes 150ms at least + await act(() => sleep(100)) + screen.getByText('data:') + await act(() => sleep(200)) + screen.getByText('data:apple, banana, pineapple,') + }) + + it('should make previousPageData null when the parallel option is enabled', async () => { + // mock api + const pageData = ['apple', 'banana', 'pineapple'] + + const previousPageDataLogs = [] + + const key = createKey() + function Page() { + const { data } = useSWRInfinite( + (index, previousPageData) => { + previousPageDataLogs.push(previousPageData) + return [key, index] + }, + ([_, index]) => createResponse(`${pageData[index]}, `, { delay: 50 }), + { + initialSize: 3, + parallel: true + } + ) + + return
data:{data}
+ } + + renderWithConfig() + screen.getByText('data:') + + // If SWR sends requests sequentially, it takes 150ms at least + await act(() => sleep(100)) + screen.getByText('data:apple, banana, pineapple,') + expect(previousPageDataLogs.every(d => d === null)).toBeTruthy() + }) })