diff --git a/_internal/types.ts b/_internal/types.ts index 249c91565..cb94a8fc5 100644 --- a/_internal/types.ts +++ b/_internal/types.ts @@ -266,7 +266,7 @@ export type MutatorOptions = { | boolean | ((result: any, currentData: Data | undefined) => Data) optimisticData?: Data | ((currentData?: Data) => Data) - rollbackOnError?: boolean + rollbackOnError?: boolean | ((error: unknown) => boolean) throwOnError?: boolean } diff --git a/_internal/utils/mutate.ts b/_internal/utils/mutate.ts index eaa513837..d07d2d45a 100644 --- a/_internal/utils/mutate.ts +++ b/_internal/utils/mutate.ts @@ -55,10 +55,16 @@ export async function internalMutate( ) let populateCache = options.populateCache + + const rollbackOnErrorOption = options.rollbackOnError let optimisticData = options.optimisticData const revalidate = options.revalidate !== false - const rollbackOnError = options.rollbackOnError !== false + const rollbackOnError = (error: unknown): boolean => { + return typeof rollbackOnErrorOption === 'function' + ? rollbackOnErrorOption(error) + : rollbackOnErrorOption !== false + } const throwOnError = options.throwOnError // If the second argument is a key filter, return the mutation results for all @@ -126,7 +132,8 @@ export async function internalMutate( // that is going to be overridden by a `committedData`, or get reverted back. // `committedData` is the validated value that comes from a fetch or mutation. const displayedData = state.data - const committedData = isUndefined(state._c) ? displayedData : state._c + const currentData = state._c + const committedData = isUndefined(currentData) ? displayedData : currentData // Do optimistic data update. if (hasOptimisticData) { @@ -162,7 +169,7 @@ export async function internalMutate( if (beforeMutationTs !== MUTATION[key][0]) { if (error) throw error return data - } else if (error && hasOptimisticData && rollbackOnError) { + } else if (error && hasOptimisticData && rollbackOnError(error)) { // Rollback. Always populate the cache in this case but without // transforming the data. populateCache = true diff --git a/test/use-swr-local-mutation.test.tsx b/test/use-swr-local-mutation.test.tsx index 7e30cf20c..1a3c328d2 100644 --- a/test/use-swr-local-mutation.test.tsx +++ b/test/use-swr-local-mutation.test.tsx @@ -1337,7 +1337,7 @@ describe('useSWR - local mutation', () => { expect(renderedData).toEqual([undefined, 'foo', 'bar', 'baz', 'qux', 'baz']) }) - it('should not rollback optimistic updates if `rollbackOnError`', async () => { + it('should not rollback optimistic updates if `rollbackOnError` is disabled', async () => { const key = createKey() const renderedData = [] let mutate @@ -1362,17 +1362,36 @@ describe('useSWR - local mutation', () => { try { await executeWithoutBatching(() => - mutate(createResponse(new Error('baz'), { delay: 20 }), { - optimisticData: 'bar', + mutate(createResponse(new Error('baz-1'), { delay: 20 }), { + optimisticData: 'bar-1', rollbackOnError: false }) ) } catch (e) { - expect(e.message).toEqual('baz') + expect(e.message).toEqual('baz-1') + } + + await sleep(30) + expect(renderedData).toEqual([undefined, 0, 'bar-1', 1]) + + let rollbackErrorMessage + try { + await executeWithoutBatching(() => + mutate(createResponse(new Error('baz-2'), { delay: 20 }), { + optimisticData: 'bar-2', + rollbackOnError: error => { + rollbackErrorMessage = error.message + return false + } + }) + ) + } catch (e) { + expect(e.message).toEqual('baz-2') } await sleep(30) - expect(renderedData).toEqual([undefined, 0, 'bar', 1]) + expect(renderedData).toEqual([undefined, 0, 'bar-1', 1, 'bar-2', 2]) + expect(rollbackErrorMessage).toEqual('baz-2') }) it('should support transforming the result with `populateCache` before writing back', async () => {