From a62a925bab44b6e6e7ec3dd4f52732d1f81540fc Mon Sep 17 00:00:00 2001 From: Mingwei Pan Date: Tue, 11 Jul 2023 13:01:47 +0800 Subject: [PATCH] fix(mutate): fix types of mutate/trigger; make mutate/trigger always return the result of MutatorCallback --- _internal/src/types.ts | 45 ++++++++++++++++++++-------- _internal/src/utils/cache.ts | 9 ++---- _internal/src/utils/config.ts | 2 +- _internal/src/utils/mutate.ts | 24 +++++++-------- core/src/use-swr.ts | 2 +- infinite/src/index.ts | 14 ++++----- mutation/src/types.ts | 38 +++++++++++++++++++---- subscription/src/types.ts | 2 +- test/type/mutate.ts | 6 ++-- test/use-swr-infinite.test.tsx | 4 +-- test/use-swr-local-mutation.test.tsx | 6 ++-- 11 files changed, 94 insertions(+), 58 deletions(-) diff --git a/_internal/src/types.ts b/_internal/src/types.ts index 4d0aef278..139c8f1a7 100644 --- a/_internal/src/types.ts +++ b/_internal/src/types.ts @@ -310,11 +310,15 @@ export type MutatorCallback = ( currentData?: Data ) => Promise | undefined | Data -export type MutatorOptions = { +/** + * @typeParam Data - The type of the data related to the key + * @typeParam MutationData - The type of the data returned by the mutator + */ +export type MutatorOptions = { revalidate?: boolean populateCache?: | boolean - | ((result: any, currentData: Data | undefined) => Data) + | ((result: MutationData, currentData: Data | undefined) => Data) optimisticData?: | Data | ((currentData: Data | undefined, displayedData: Data | undefined) => Data) @@ -365,23 +369,38 @@ export type MutatorWrapper = Fn extends ( export type Mutator = MutatorWrapper> -export interface ScopedMutator { - ( +export interface ScopedMutator { + /** + * @typeParam Data - The type of the data related to the key + * @typeParam MutationData - The type of the data returned by the mutator + */ + ( matcher: (key?: Arguments) => boolean, - data?: T | Promise | MutatorCallback, - opts?: boolean | MutatorOptions - ): Promise> - ( + data?: MutationData | Promise | MutatorCallback, + opts?: boolean | MutatorOptions + ): Promise> + /** + * @typeParam Data - The type of the data related to the key + * @typeParam MutationData - The type of the data returned by the mutator + */ + ( key: Arguments, data?: T | Promise | MutatorCallback, - opts?: boolean | MutatorOptions + opts?: boolean | MutatorOptions ): Promise } -export type KeyedMutator = ( - data?: Data | Promise | MutatorCallback, - opts?: boolean | MutatorOptions -) => Promise +/** + * @typeParam Data - The type of the data related to the key + * @typeParam MutationData - The type of the data returned by the mutator + */ +export type KeyedMutator = ( + data?: + | MutationData + | Promise + | MutatorCallback, + opts?: boolean | MutatorOptions +) => Promise export type SWRConfiguration< Data = any, diff --git a/_internal/src/utils/cache.ts b/_internal/src/utils/cache.ts index 23b977458..9e3112ff3 100644 --- a/_internal/src/utils/cache.ts +++ b/_internal/src/utils/cache.ts @@ -27,8 +27,8 @@ export const initCache = ( provider: Cache, options?: Partial ): - | [Cache, ScopedMutator, () => void, () => void] - | [Cache, ScopedMutator] + | [Cache, ScopedMutator, () => void, () => void] + | [Cache, ScopedMutator] | undefined => { // The global state for a specific provider will be used to deduplicate // requests and store listeners. As well as a mutate function that is bound to @@ -43,10 +43,7 @@ export const initCache = ( // new mutate function. const EVENT_REVALIDATORS = {} - const mutate = internalMutate.bind( - UNDEFINED, - provider - ) as ScopedMutator + const mutate = internalMutate.bind(UNDEFINED, provider) as ScopedMutator let unmount = noop const subscriptions: Record void)[]> = diff --git a/_internal/src/utils/config.ts b/_internal/src/utils/config.ts index 9061fab99..a2eae1b3e 100644 --- a/_internal/src/utils/config.ts +++ b/_internal/src/utils/config.ts @@ -41,7 +41,7 @@ const compare = (currentData: any, newData: any) => stableHash(currentData) == stableHash(newData) // Default cache provider -const [cache, mutate] = initCache(new Map()) as [Cache, ScopedMutator] +const [cache, mutate] = initCache(new Map()) as [Cache, ScopedMutator] export { cache, mutate, compare } // Default config diff --git a/_internal/src/utils/mutate.ts b/_internal/src/utils/mutate.ts index 4667b1b7c..0be0b0d8d 100644 --- a/_internal/src/utils/mutate.ts +++ b/_internal/src/utils/mutate.ts @@ -174,10 +174,9 @@ export async function internalMutate( // Rollback. Always populate the cache in this case but without // transforming the data. populateCache = true - data = committedData // Reset data to be the latest committed data, and clear the `_c` value. - set({ data, _c: UNDEFINED }) + set({ data: committedData, _c: UNDEFINED }) } } @@ -186,11 +185,12 @@ export async function internalMutate( if (!error) { // Transform the result into data. if (isFunction(populateCache)) { - data = populateCache(data, committedData) + const populateCachedData = populateCache(data, committedData) + set({ data: populateCachedData, error: UNDEFINED, _c: UNDEFINED }) + } else { + // Only update cached data and reset the error if there's no error. Data can be `undefined` here. + set({ data, error: UNDEFINED, _c: UNDEFINED }) } - - // Only update cached data and reset the error if there's no error. Data can be `undefined` here. - set({ data, error: UNDEFINED, _c: UNDEFINED }) } } @@ -198,17 +198,17 @@ export async function internalMutate( MUTATION[key][1] = getTimestamp() // Update existing SWR Hooks' internal states: - const res = await startRevalidate() - - // The mutation and revalidation are ended, we can clear it since the data is - // not an optimistic value anymore. - set({ _c: UNDEFINED }) + Promise.resolve(startRevalidate()).then(() => { + // The mutation and revalidation are ended, we can clear it since the data is + // not an optimistic value anymore. + set({ _c: UNDEFINED }) + }) // Throw error or return data if (error) { if (throwOnError) throw error return } - return populateCache ? res : data + return data } } diff --git a/core/src/use-swr.ts b/core/src/use-swr.ts index 72054298f..d4d18fcf8 100644 --- a/core/src/use-swr.ts +++ b/core/src/use-swr.ts @@ -550,7 +550,7 @@ export const useSWRHandler = ( // eslint-disable-next-line react-hooks/exhaustive-deps const boundMutate: SWRResponse['mutate'] = useCallback( // Use callback to make sure `keyRef.current` returns latest result every time - (...args) => { + (...args: any[]) => { return internalMutate(cache, keyRef.current, ...args) }, // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/infinite/src/index.ts b/infinite/src/index.ts index b31bbc0b7..58089f31c 100644 --- a/infinite/src/index.ts +++ b/infinite/src/index.ts @@ -232,13 +232,9 @@ export const infinite = ((useSWRNext: SWRHook) => const mutate = useCallback( // eslint-disable-next-line func-names - function ( - data?: - | undefined - | Data[] - | Promise - | MutatorCallback, - opts?: undefined | boolean | MutatorOptions + function ( + data?: undefined | T | Promise | MutatorCallback, + opts?: undefined | boolean | MutatorOptions ) { // When passing as a boolean, it's explicitly used to disable/enable // revalidation. @@ -261,8 +257,8 @@ export const infinite = ((useSWRNext: SWRHook) => } return arguments.length - ? swr.mutate(data, { ...options, revalidate: shouldRevalidate }) - : swr.mutate() + ? swr.mutate(data, { ...options, revalidate: shouldRevalidate }) + : swr.mutate() }, // swr.mutate is always the same reference // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/mutation/src/types.ts b/mutation/src/types.ts index 836f39e35..086808a43 100644 --- a/mutation/src/types.ts +++ b/mutation/src/types.ts @@ -190,7 +190,13 @@ export interface SWRMutationResponse< } export interface SWRMutationHook { - ( + < + Data = any, + Error = any, + SWRMutationKey extends Key = Key, + ExtraArg = never, + SWRData = Data + >( /** * The key of the resource that will be mutated. It should be the same key * used in the `useSWR` hook so SWR can handle revalidation and race @@ -212,9 +218,21 @@ export interface SWRMutationHook { /** * Extra options for the mutation hook. */ - options?: SWRMutationConfiguration + options?: SWRMutationConfiguration< + Data, + Error, + SWRMutationKey, + ExtraArg, + SWRData + > ): SWRMutationResponse - ( + < + Data = any, + Error = any, + SWRMutationKey extends Key = Key, + ExtraArg = never, + SWRData = Data + >( /** * The key of the resource that will be mutated. It should be the same key * used in the `useSWR` hook so SWR can handle revalidation and race @@ -240,10 +258,17 @@ export interface SWRMutationHook { Data, Error, SWRMutationKey, - ExtraArg + ExtraArg, + SWRData > & { throwOnError: false } ): SWRMutationResponse - ( + < + Data = any, + Error = any, + SWRMutationKey extends Key = Key, + ExtraArg = never, + SWRData = Data + >( /** * The key of the resource that will be mutated. It should be the same key * used in the `useSWR` hook so SWR can handle revalidation and race @@ -269,7 +294,8 @@ export interface SWRMutationHook { Data, Error, SWRMutationKey, - ExtraArg + ExtraArg, + SWRData > & { throwOnError: true } ): SWRMutationResponse } diff --git a/subscription/src/types.ts b/subscription/src/types.ts index 1038258c4..6cda5e0d1 100644 --- a/subscription/src/types.ts +++ b/subscription/src/types.ts @@ -1,7 +1,7 @@ import type { Key, SWRConfiguration, MutatorCallback } from 'swr' export type SWRSubscriptionOptions = { - next: (err?: Error | null, data?: Data | MutatorCallback) => void + next: (err?: Error | null, data?: Data | MutatorCallback) => void } export type SWRSubscription< diff --git a/test/type/mutate.ts b/test/type/mutate.ts index 90b9222eb..9b153c39c 100644 --- a/test/type/mutate.ts +++ b/test/type/mutate.ts @@ -62,11 +62,9 @@ export function useMutatorTypes() { mutate(async () => '1') - // @ts-expect-error mutate(async () => 1) - // FIXME: this should work. - // mutate(async () => 1, { populateCache: false }) + mutate(async () => 1, { populateCache: false }) } export function useConfigMutate() { @@ -85,7 +83,7 @@ export function useConfigMutate() { ) expect>( - mutate('string', data => { + mutate('string', (data?: string) => { expectType(data) return '0' }) diff --git a/test/use-swr-infinite.test.tsx b/test/use-swr-infinite.test.tsx index 18678550c..e8e3fd055 100644 --- a/test/use-swr-infinite.test.tsx +++ b/test/use-swr-infinite.test.tsx @@ -1394,7 +1394,7 @@ describe('useSWRInfinite', () => {