Skip to content

Commit

Permalink
feat: add the parallel option in useSWRInfinite (#2404)
Browse files Browse the repository at this point in the history
* feat: add parallel option in useSWRInfinite

* test: add more tests for the parallel option

* chore: rename a local variable

* test: add comments for help

* test: the parallel option with an error
  • Loading branch information
koba04 committed Feb 27, 2023
1 parent 02c7d1a commit 2464ed5
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 6 deletions.
33 changes: 27 additions & 6 deletions infinite/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ export const infinite = (<Data, Error>(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
Expand Down Expand Up @@ -138,9 +139,13 @@ export const infinite = (<Data, Error>(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.
Expand Down Expand Up @@ -173,11 +178,27 @@ export const infinite = (<Data, Error>(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
Expand Down
1 change: 1 addition & 0 deletions infinite/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface SWRInfiniteConfiguration<
revalidateAll?: boolean
persistSize?: boolean
revalidateFirstPage?: boolean
parallel?: boolean
fetcher?: Fn
}

Expand Down
149 changes: 149 additions & 0 deletions test/use-swr-infinite.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <div>data:{data}</div>
}

renderWithConfig(<Page />)
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<string>(pageData[index].data as string, {
delay: pageData[index].delay
}),
{
initialSize: 3,
parallel: true
}
)

if (error) {
return <div>error:{error.message}</div>
}

return <div>data:{data}</div>
}

renderWithConfig(<Page />)
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 <div>data:{data}</div>
}

renderWithConfig(<Page />)
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 <div>data:{data}</div>
}

renderWithConfig(<Page />)
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 <div>data:{data}</div>
}

renderWithConfig(<Page />)
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()
})
})

0 comments on commit 2464ed5

Please sign in to comment.