Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add the parallel option in useSWRInfinite #2404

Merged
merged 6 commits into from
Feb 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
})
})