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

fix(mutate): fix types of mutate/trigger; make mutate/trigger always return the result of fetcher #2708

Merged
merged 2 commits into from
Aug 11, 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
45 changes: 32 additions & 13 deletions _internal/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,11 +310,15 @@ export type MutatorCallback<Data = any> = (
currentData?: Data
) => Promise<undefined | Data> | undefined | Data

export type MutatorOptions<Data = any> = {
/**
* @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<Data = any, MutationData = Data> = {
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)
Expand Down Expand Up @@ -365,23 +369,38 @@ export type MutatorWrapper<Fn> = Fn extends (

export type Mutator<Data = any> = MutatorWrapper<MutatorFn<Data>>

export interface ScopedMutator<Data = any> {
<T = Data>(
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
*/
<Data = any, MutationData = Data>(
matcher: (key?: Arguments) => boolean,
data?: T | Promise<T> | MutatorCallback<T>,
opts?: boolean | MutatorOptions<Data>
): Promise<Array<T | undefined>>
<T = Data>(
data?: MutationData | Promise<MutationData> | MutatorCallback<MutationData>,
opts?: boolean | MutatorOptions<Data, MutationData>
): Promise<Array<MutationData | undefined>>
/**
* @typeParam Data - The type of the data related to the key
* @typeParam MutationData - The type of the data returned by the mutator
*/
<Data = any, T = Data>(
key: Arguments,
data?: T | Promise<T> | MutatorCallback<T>,
opts?: boolean | MutatorOptions<Data>
opts?: boolean | MutatorOptions<Data, T>
): Promise<T | undefined>
}

export type KeyedMutator<Data> = (
data?: Data | Promise<Data | undefined> | MutatorCallback<Data>,
opts?: boolean | MutatorOptions<Data>
) => Promise<Data | undefined>
/**
* @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>(
data?:
| MutationData
| Promise<MutationData | undefined>
| MutatorCallback<MutationData>,
opts?: boolean | MutatorOptions<Data, MutationData>
) => Promise<MutationData | undefined>

export type SWRConfiguration<
Data = any,
Expand Down
9 changes: 3 additions & 6 deletions _internal/src/utils/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ export const initCache = <Data = any>(
provider: Cache<Data>,
options?: Partial<ProviderConfiguration>
):
| [Cache<Data>, ScopedMutator<Data>, () => void, () => void]
| [Cache<Data>, ScopedMutator<Data>]
| [Cache<Data>, ScopedMutator, () => void, () => void]
| [Cache<Data>, 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
Expand All @@ -43,10 +43,7 @@ export const initCache = <Data = any>(
// new mutate function.
const EVENT_REVALIDATORS = {}

const mutate = internalMutate.bind(
UNDEFINED,
provider
) as ScopedMutator<Data>
const mutate = internalMutate.bind(UNDEFINED, provider) as ScopedMutator
let unmount = noop

const subscriptions: Record<string, ((current: any, prev: any) => void)[]> =
Expand Down
2 changes: 1 addition & 1 deletion _internal/src/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<any>, ScopedMutator<any>]
const [cache, mutate] = initCache(new Map()) as [Cache<any>, ScopedMutator]
export { cache, mutate, compare }

// Default config
Expand Down
24 changes: 12 additions & 12 deletions _internal/src/utils/mutate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,9 @@ export async function internalMutate<Data>(
// 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 })
}
}

Expand All @@ -186,29 +185,30 @@ export async function internalMutate<Data>(
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 })
}
}

// Reset the timestamp to mark the mutation has ended.
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
}
}
2 changes: 1 addition & 1 deletion core/src/use-swr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,7 @@ export const useSWRHandler = <Data = any, Error = any>(
// eslint-disable-next-line react-hooks/exhaustive-deps
const boundMutate: SWRResponse<Data, Error>['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
Expand Down
14 changes: 5 additions & 9 deletions infinite/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,13 +232,9 @@ export const infinite = (<Data, Error>(useSWRNext: SWRHook) =>

const mutate = useCallback(
// eslint-disable-next-line func-names
function (
data?:
| undefined
| Data[]
| Promise<Data[] | undefined>
| MutatorCallback<Data[]>,
opts?: undefined | boolean | MutatorOptions<Data[]>
function <T>(
data?: undefined | T | Promise<T | undefined> | MutatorCallback<T>,
opts?: undefined | boolean | MutatorOptions<Data[], T>
) {
// When passing as a boolean, it's explicitly used to disable/enable
// revalidation.
Expand All @@ -261,8 +257,8 @@ export const infinite = (<Data, Error>(useSWRNext: SWRHook) =>
}

return arguments.length
? swr.mutate(data, { ...options, revalidate: shouldRevalidate })
: swr.mutate()
? swr.mutate<T>(data, { ...options, revalidate: shouldRevalidate })
: swr.mutate<T>()
},
// swr.mutate is always the same reference
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down
38 changes: 32 additions & 6 deletions mutation/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,13 @@ export interface SWRMutationResponse<
}

export interface SWRMutationHook {
<Data = any, Error = any, SWRMutationKey extends Key = Key, ExtraArg = never>(
<
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
Expand All @@ -212,9 +218,21 @@ export interface SWRMutationHook {
/**
* Extra options for the mutation hook.
*/
options?: SWRMutationConfiguration<Data, Error, SWRMutationKey, ExtraArg>
options?: SWRMutationConfiguration<
Data,
Error,
SWRMutationKey,
ExtraArg,
SWRData
>
): SWRMutationResponse<Data, Error, SWRMutationKey, ExtraArg>
<Data = any, Error = any, SWRMutationKey extends Key = Key, ExtraArg = never>(
<
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
Expand All @@ -240,10 +258,17 @@ export interface SWRMutationHook {
Data,
Error,
SWRMutationKey,
ExtraArg
ExtraArg,
SWRData
> & { throwOnError: false }
): SWRMutationResponse<Data | undefined, Error, SWRMutationKey, ExtraArg>
<Data = any, Error = any, SWRMutationKey extends Key = Key, ExtraArg = never>(
<
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
Expand All @@ -269,7 +294,8 @@ export interface SWRMutationHook {
Data,
Error,
SWRMutationKey,
ExtraArg
ExtraArg,
SWRData
> & { throwOnError: true }
): SWRMutationResponse<Data, Error, SWRMutationKey, ExtraArg>
}
2 changes: 1 addition & 1 deletion subscription/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Key, SWRConfiguration, MutatorCallback } from 'swr'

export type SWRSubscriptionOptions<Data = any, Error = any> = {
next: <T = Data>(err?: Error | null, data?: Data | MutatorCallback<T>) => void
next: (err?: Error | null, data?: Data | MutatorCallback<Data>) => void
}

export type SWRSubscription<
Expand Down
6 changes: 2 additions & 4 deletions test/type/mutate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -85,7 +83,7 @@ export function useConfigMutate() {
)

expect<Promise<any>>(
mutate('string', data => {
mutate('string', (data?: string) => {
expectType<string | undefined>(data)
return '0'
})
Expand Down
4 changes: 2 additions & 2 deletions test/use-swr-infinite.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1394,7 +1394,7 @@ describe('useSWRInfinite', () => {
<button
onClick={() => {
mutate(updater, {
populateCache: (result: string, currentData: string[]) => {
populateCache: (result: string[], currentData: string[]) => {
return [...currentData, ...result]
},
revalidate: false
Expand Down Expand Up @@ -1476,7 +1476,7 @@ describe('useSWRInfinite', () => {
onClick={() => {
mutate(updater, {
optimisticData: current => [current[0], [...current[1], 'B4']],
populateCache: (result: string, currentData: string[]) => {
populateCache: (result: string[], currentData: string[]) => {
return [currentData[0], [...currentData[1], ...result]]
},
revalidate: false
Expand Down
6 changes: 3 additions & 3 deletions test/use-swr-local-mutation.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1365,7 +1365,7 @@ describe('useSWR - local mutation', () => {
}
)
})

await sleep(30)
try {
// data == "baz", then reverted back to "bar"
await executeWithoutBatching(() =>
Expand Down Expand Up @@ -1567,8 +1567,8 @@ describe('useSWR - local mutation', () => {

let appendData

const sendRequest = <Data,>(newItem) => {
return new Promise<Data>(res =>
const sendRequest = (newItem: string) => {
return new Promise<string>(res =>
setTimeout(() => {
// The server capitalizes the new item.
const modifiedData =
Expand Down
Loading