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

Pass displayed data as second parameter of functional optimistic data #2668

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
4 changes: 3 additions & 1 deletion _internal/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,9 @@ export type MutatorOptions<Data = any> = {
populateCache?:
| boolean
| ((result: any, currentData: Data | undefined) => Data)
optimisticData?: Data | ((currentData?: Data) => Data)
optimisticData?:
| Data
| ((currentData: Data | undefined, displayedData: Data | undefined) => Data)
rollbackOnError?: boolean | ((error: unknown) => boolean)
throwOnError?: boolean
}
Expand Down
2 changes: 1 addition & 1 deletion _internal/src/utils/mutate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export async function internalMutate<Data>(
// Do optimistic data update.
if (hasOptimisticData) {
optimisticData = isFunction(optimisticData)
? optimisticData(committedData)
? optimisticData(committedData, displayedData)
: optimisticData

// When we set optimistic data, backup the current committedData data in `_c`.
Expand Down
84 changes: 84 additions & 0 deletions test/use-swr-local-mutation.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1123,6 +1123,90 @@ describe('useSWR - local mutation', () => {
expect(renderedData).toEqual([undefined, 'loading', 'final'])
})

it('should be able to use functional optimistic data config and use second param `displayedData` to keep UI consistent in slow networks', async () => {
const key1 = createKey()
const key2 = createKey()
let data1 = 0
let data2 = 0

function useOptimisticData1Mutate() {
const { mutate } = useSWRConfig()
return () => {
return mutate(key1, () => createResponse(data1++, { delay: 1000 }), {
optimisticData(currentData) {
return currentData + 1 // optimistic update current data
}
})
}
}

function useOptimisticData2Mutate() {
const { mutate } = useSWRConfig()
return () => {
return mutate(key2, () => createResponse(data2++, { delay: 1000 }), {
optimisticData(_, displayedData) {
return displayedData + 1 // optimistic update displayed data
}
})
}
}

function Page() {
const mutateWithOptimisticallyUpdatedCurrentData =
useOptimisticData1Mutate()
const mutateWithOptimisticallyUpdatedDisplayedData =
useOptimisticData2Mutate()
const { data: renderedData1 } = useSWR<number>(key1, () =>
createResponse(data1, { delay: 1000 })
)
const { data: renderedData2 } = useSWR<number>(key2, () =>
createResponse(data2, { delay: 1000 })
)

return (
<div>
<button onClick={mutateWithOptimisticallyUpdatedCurrentData}>
incrementCurrent
</button>
<button onClick={mutateWithOptimisticallyUpdatedDisplayedData}>
incrementDisplayed
</button>
<div>
data: <span data-testid="data1">{renderedData1}</span>
</div>
<div>
data: <span data-testid="data2">{renderedData2}</span>
</div>
</div>
)
}

renderWithConfig(<Page />)
await act(() => sleep(1000)) // Wait for initial data to load
fireEvent.click(screen.getByText('incrementCurrent'))
fireEvent.click(screen.getByText('incrementDisplayed'))
fireEvent.click(screen.getByText('incrementCurrent'))
fireEvent.click(screen.getByText('incrementDisplayed'))
const renderedData1 = parseInt(
(await screen.findByTestId('data1')).innerHTML,
10
)
const renderedData2 = Number((await screen.findByTestId('data2')).innerHTML)
await act(() => sleep(2000)) // Wait for revalidation roundtrip
const renderedRevalidatedData1 = Number(
(await screen.findByTestId('data1')).innerHTML
)
const renderedRevalidatedData2 = Number(
(await screen.findByTestId('data2')).innerHTML
)
expect(data1).toEqual(2)
expect(renderedData1).toEqual(1)
expect(renderedRevalidatedData1).toEqual(2)
expect(data2).toEqual(2)
expect(renderedData2).toEqual(2)
expect(renderedRevalidatedData2).toEqual(2)
})

it('should prevent race conditions with optimistic UI', async () => {
const key = createKey()
const renderedData = []
Expand Down