From 6a0d82902ce30f69dd75db76abd50b3783cbe058 Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Thu, 2 Feb 2023 19:42:40 +0900 Subject: [PATCH 1/5] feat: add parallel option in useSWRInfinite --- infinite/index.ts | 33 +++++++++++++++++++++++++++------ infinite/types.ts | 1 + test/use-swr-infinite.test.tsx | 25 +++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/infinite/index.ts b/infinite/index.ts index 3b99169d7..7b4bad6f3 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 revalidates = [] + 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) { + revalidates.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(revalidates.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..ece0a8c01 100644 --- a/test/use-swr-infinite.test.tsx +++ b/test/use-swr-infinite.test.tsx @@ -1692,4 +1692,29 @@ 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: 100 }), + { + initialSize: 3, + parallel: true + } + ) + + return
data:{data}
+ } + + renderWithConfig() + screen.getByText('data:') + + await act(() => sleep(150)) + screen.getByText('data:apple, banana, pineapple,') + }) }) From 2a2ad60a58a05bea391fa38cd9549a17031ffc83 Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Sun, 5 Feb 2023 22:25:14 +0900 Subject: [PATCH 2/5] test: add more tests for the parallel option --- test/use-swr-infinite.test.tsx | 88 +++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/test/use-swr-infinite.test.tsx b/test/use-swr-infinite.test.tsx index ece0a8c01..87980884d 100644 --- a/test/use-swr-infinite.test.tsx +++ b/test/use-swr-infinite.test.tsx @@ -1701,7 +1701,7 @@ describe('useSWRInfinite', () => { function Page() { const { data } = useSWRInfinite( index => [key, index], - ([_, index]) => createResponse(`${pageData[index]}, `, { delay: 100 }), + ([_, index]) => createResponse(`${pageData[index]}, `, { delay: 50 }), { initialSize: 3, parallel: true @@ -1714,7 +1714,91 @@ describe('useSWRInfinite', () => { renderWithConfig() screen.getByText('data:') - await act(() => sleep(150)) + await act(() => sleep(100)) + screen.getByText('data:apple, banana, pineapple,') + }) + + 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:') + + 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:') + + 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:') + + await act(() => sleep(100)) screen.getByText('data:apple, banana, pineapple,') + expect(previousPageDataLogs.every(d => d === null)).toBeTruthy() }) }) From 790a92a33cce467dfba5e9a83985c0107af06a73 Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Sun, 5 Feb 2023 22:26:55 +0900 Subject: [PATCH 3/5] chore: rename a local variable --- infinite/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infinite/index.ts b/infinite/index.ts index 7b4bad6f3..6528d63f8 100644 --- a/infinite/index.ts +++ b/infinite/index.ts @@ -139,7 +139,7 @@ export const infinite = ((useSWRNext: SWRHook) => const pageSize = resolvePageSize() - const revalidates = [] + const revalidators = [] let previousPageData = null for (let i = 0; i < pageSize; ++i) { @@ -184,7 +184,7 @@ export const infinite = ((useSWRNext: SWRHook) => data[i] = pageData } if (parallel) { - revalidates.push(revalidate) + revalidators.push(revalidate) } else { await revalidate() } @@ -198,7 +198,7 @@ export const infinite = ((useSWRNext: SWRHook) => // flush all revalidateions in parallel if (parallel) { - await Promise.all(revalidates.map(r => r())) + await Promise.all(revalidators.map(r => r())) } // once we executed the data fetching based on the context, clear the context From 7d1ac07c1303099ccfda119e6a03b1de0d0d8d85 Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Tue, 7 Feb 2023 22:29:00 +0900 Subject: [PATCH 4/5] test: add comments for help --- test/use-swr-infinite.test.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/use-swr-infinite.test.tsx b/test/use-swr-infinite.test.tsx index 87980884d..28572f99e 100644 --- a/test/use-swr-infinite.test.tsx +++ b/test/use-swr-infinite.test.tsx @@ -1714,6 +1714,7 @@ describe('useSWRInfinite', () => { renderWithConfig() screen.getByText('data:') + // If SWR sends requests sequentially, it takes 150ms at least await act(() => sleep(100)) screen.getByText('data:apple, banana, pineapple,') }) @@ -1739,6 +1740,7 @@ describe('useSWRInfinite', () => { 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)) @@ -1765,6 +1767,7 @@ describe('useSWRInfinite', () => { 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)) @@ -1797,6 +1800,7 @@ describe('useSWRInfinite', () => { 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() From 5ee0783b5f6e7a7dbd53ce72eb946f7df9a3c55e Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Tue, 7 Feb 2023 22:40:21 +0900 Subject: [PATCH 5/5] test: the parallel option with an error --- test/use-swr-infinite.test.tsx | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/use-swr-infinite.test.tsx b/test/use-swr-infinite.test.tsx index 28572f99e..b70ac9d82 100644 --- a/test/use-swr-infinite.test.tsx +++ b/test/use-swr-infinite.test.tsx @@ -1719,6 +1719,42 @@ describe('useSWRInfinite', () => { 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']