From ca3a138d41a3e2a344e1aacbcce70e3160027d3c Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Fri, 24 Dec 2021 19:12:51 +0100 Subject: [PATCH] feat: Add `populateCache` option to `mutate` (#1729) * add populateCache option to mutate * rename type and export it --- src/index.ts | 1 + src/types.ts | 16 +++++--- src/utils/broadcast-state.ts | 7 ++-- src/utils/mutate.ts | 61 ++++++++++++++++++---------- test/use-swr-local-mutation.test.tsx | 29 +++++++++++++ 5 files changed, 84 insertions(+), 30 deletions(-) diff --git a/src/index.ts b/src/index.ts index 47b5e9831..d7258103a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,6 +20,7 @@ export { SWRHook, Fetcher, MutatorCallback, + MutatorOptions, Middleware, Arguments } from './types' diff --git a/src/types.ts b/src/types.ts index dfe67fb69..a3e737d76 100644 --- a/src/types.ts +++ b/src/types.ts @@ -145,13 +145,19 @@ export type MutatorCallback = ( currentValue?: Data ) => Promise | undefined | Data +export type MutatorOptions = { + revalidate?: boolean + populateCache?: boolean +} + export type Broadcaster = ( cache: Cache, key: string, data: Data, error?: Error, isValidating?: boolean, - shouldRevalidate?: boolean + revalidate?: boolean, + populateCache?: boolean ) => Promise export type State = { @@ -164,7 +170,7 @@ export type Mutator = ( cache: Cache, key: Key, data?: Data | Promise | MutatorCallback, - shouldRevalidate?: boolean + opts?: boolean | MutatorOptions ) => Promise export interface ScopedMutator { @@ -172,19 +178,19 @@ export interface ScopedMutator { ( key: Key, data?: Data | Promise | MutatorCallback, - shouldRevalidate?: boolean + opts?: boolean | MutatorOptions ): Promise /** This is used for global mutator */ ( key: Key, data?: T | Promise | MutatorCallback, - shouldRevalidate?: boolean + opts?: boolean | MutatorOptions ): Promise } export type KeyedMutator = ( data?: Data | Promise | MutatorCallback, - shouldRevalidate?: boolean + opts?: boolean | MutatorOptions ) => Promise // Public types diff --git a/src/utils/broadcast-state.ts b/src/utils/broadcast-state.ts index 2ce246c80..3415b36e1 100644 --- a/src/utils/broadcast-state.ts +++ b/src/utils/broadcast-state.ts @@ -8,7 +8,8 @@ export const broadcastState: Broadcaster = ( data, error, isValidating, - revalidate + revalidate, + populateCache = true ) => { const [ EVENT_REVALIDATORS, @@ -21,8 +22,8 @@ export const broadcastState: Broadcaster = ( const revalidators = EVENT_REVALIDATORS[key] const updaters = STATE_UPDATERS[key] - // Always update states of all hooks. - if (updaters) { + // Cache was populated, update states of all hooks. + if (populateCache && updaters) { for (let i = 0; i < updaters.length; ++i) { updaters[i](data, error, isValidating) } diff --git a/src/utils/mutate.ts b/src/utils/mutate.ts index 924c5e291..8f349cf0f 100644 --- a/src/utils/mutate.ts +++ b/src/utils/mutate.ts @@ -4,14 +4,28 @@ import { SWRGlobalState, GlobalState } from './global-state' import { broadcastState } from './broadcast-state' import { getTimestamp } from './timestamp' -import { Key, Cache, MutatorCallback } from '../types' +import { Key, Cache, MutatorCallback, MutatorOptions } from '../types' export const internalMutate = async ( - cache: Cache, - _key: Key, - _data?: Data | Promise | MutatorCallback, - revalidate = true + ...args: [ + Cache, + Key, + undefined | Data | Promise | MutatorCallback, + undefined | boolean | MutatorOptions + ] ) => { + const [cache, _key, _data, _opts] = args + + // When passing as a boolean, it's explicitily used to disable/enable + // revalidation. + const options = + typeof _opts === 'boolean' ? { revalidate: _opts } : _opts || {} + + // Fallback to `true` if it's not explicitly set to `false` + const revalidate = options.revalidate !== false + const populateCache = options.populateCache !== false + + // Serilaize key const [key, , keyErr] = serialize(_key) if (!key) return @@ -28,20 +42,22 @@ export const internalMutate = async ( cache.get(key), cache.get(keyErr), UNDEFINED, - revalidate + revalidate, + populateCache ) } - let data: any, error: unknown + let data: any = _data + let error: unknown // Update global timestamps. const beforeMutationTs = (MUTATION_TS[key] = getTimestamp()) MUTATION_END_TS[key] = 0 - if (isFunction(_data)) { - // `_data` is a function, call it passing current cache value. + if (isFunction(data)) { + // `data` is a function, call it passing current cache value. try { - _data = (_data as MutatorCallback)(cache.get(key)) + data = (data as MutatorCallback)(cache.get(key)) } catch (err) { // if `_data` function throws an error synchronously, it shouldn't be cached _data = UNDEFINED @@ -49,11 +65,11 @@ export const internalMutate = async ( } } - // `_data` is a promise/thenable, resolve the final data first. - if (_data && isFunction((_data as Promise).then)) { + // `data` is a promise/thenable, resolve the final data first. + if (data && isFunction((data as Promise).then)) { // This means that the mutation is async, we need to check timestamps to // avoid race conditions. - data = await (_data as Promise).catch(err => { + data = await (data as Promise).catch(err => { error = err }) @@ -64,16 +80,16 @@ export const internalMutate = async ( if (error) throw error return data } - } else { - data = _data } - if (!isUndefined(data)) { - // update cached data - cache.set(key, data) + if (populateCache) { + if (!error) { + // Only update cached data if there's no error. Data can be `undefined` here. + cache.set(key, data) + } + // Always update or reset the error. + cache.set(keyErr, error) } - // Always update or reset the error. - cache.set(keyErr, error) // Reset the timestamp to mark the mutation has ended MUTATION_END_TS[key] = getTimestamp() @@ -85,10 +101,11 @@ export const internalMutate = async ( data, error, UNDEFINED, - revalidate + revalidate, + populateCache ) // Throw error or return data if (error) throw error - return res + return populateCache ? res : data } diff --git a/test/use-swr-local-mutation.test.tsx b/test/use-swr-local-mutation.test.tsx index 959751f32..04e9e9d48 100644 --- a/test/use-swr-local-mutation.test.tsx +++ b/test/use-swr-local-mutation.test.tsx @@ -982,4 +982,33 @@ describe('useSWR - local mutation', () => { await sleep(300) await screen.findByText('success') }) + + it('should not update the cache when `populateCache` is disabled', async () => { + const key = createKey() + function Page() { + const { data, mutate } = useSWR(key, () => 'foo') + return ( + <> +
data: {String(data)}
+ + + ) + } + + renderWithConfig() + await screen.findByText('data: foo') + + fireEvent.click(screen.getByText('mutate')) + await sleep(30) + await screen.findByText('data: foo') + }) })