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: fix the type of the argument of KeyedMutator for populateCache #2933

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
30 changes: 22 additions & 8 deletions src/_internal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -428,21 +428,35 @@ 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, T = Data>(
<Data = any, MutationData = Data>(
key: Arguments,
data?: T | Promise<T> | MutatorCallback<T>,
opts?: boolean | MutatorOptions<Data, T>
): Promise<T | undefined>
data?: MutationData | Promise<MutationData> | MutatorCallback<MutationData>,
opts?: boolean | MutatorOptions<Data, MutationData>
): Promise<MutationData | undefined>
Comment on lines +431 to +435
Copy link
Author

@Key5n Key5n Apr 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just refactoring for name consistency.
Since there is @typeParam MutationData comment docs, I think the generics parameter T should follow that.

}

/**
* @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>(
data?: Data | Promise<Data | undefined> | MutatorCallback<Data>,
opts?: boolean | MutatorOptions<Data, MutationData>
) => Promise<Data | MutationData | undefined>
export type KeyedMutator<Data> = {
(
data?: Data | Promise<Data | undefined> | MutatorCallback<Data>,
opts?: boolean | MutatorOptions<Data, Data>
): Promise<Data | undefined>
<MutationData = Data>(
data:
| MutationData
| Promise<MutationData | undefined>
| MutatorCallback<MutationData>,
opts: Omit<MutatorOptions<Data, MutationData>, 'populateCache'> & {
populateCache: (
result: MutationData,
currentData: Data | undefined
) => Data
}
): Promise<Data | MutationData | undefined>
}

export type SWRConfiguration<
Data = any,
Expand Down
12 changes: 2 additions & 10 deletions src/infinite/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
import type {
BareFetcher,
SWRHook,
MutatorCallback,
Middleware,
GlobalState
} from '../_internal'
Expand Down Expand Up @@ -238,16 +237,9 @@ export const infinite = (<Data, Error>(useSWRNext: SWRHook) =>
config
)

const mutate = useCallback(
const mutate = useCallback<SWRInfiniteKeyedMutator<Data[]>>(
// eslint-disable-next-line func-names
function <T = Data[]>(
data?:
| undefined
| Data[]
| Promise<Data[] | undefined>
| MutatorCallback<Data[]>,
opts?: undefined | boolean | SWRInfiniteMutatorOptions<Data[], T>
) {
function (data?: any, opts?: any) {
Copy link
Author

@Key5n Key5n Jun 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I request you for comments for attaching any type to the arguments.

The reason for attachching any is below.

I thought the type of the arguments data and opts should be any when using function overload since signatures in KeyedMutator couldn't be distinguished at this point.

The type of data argument at this point should be Data | Promise<Data | undefined> | MutatorCallback<Data>| MutationData | Promise<MutationData | undefined> | MutatorCallback<MutationData> | undefined.

But I I do so and pass the data to swr.mutate, It gives the following error.

No overload matches this call.
  Overload 1 of 2, '(data?: Data[] | Promise<Data[] | undefined> | MutatorCallback<Data[]> | undefined, opts?: boolean | MutatorOptions<Data[], Data[]> | undefined): Promise<...>', gave the following error.
    Argument of type 'Data[] | MutationData | Promise<Data[] | undefined> | MutatorCallback<Data[]> | Promise<MutationData | undefined> | MutatorCallback<...> | undefined' is not assignable to parameter of type 'Data[] | Promise<Data[] | undefined> | MutatorCallback<Data[]> | undefined'.
      Type 'MutationData' is not assignable to type 'Data[] | Promise<Data[] | undefined> | MutatorCallback<Data[]> | undefined'.
        Type 'MutationData' is not assignable to type 'Promise<Data[] | undefined>'.
  Overload 2 of 2, '(data: MutationData | Promise<MutationData | undefined> | MutatorCallback<MutationData>, opts: Omit<...> & { ...; }): Promise<...>', gave the following error.
    Argument of type 'Data[] | MutationData | Promise<Data[] | undefined> | MutatorCallback<Data[]> | Promise<MutationData | undefined> | MutatorCallback<...> | undefined' is not assignable to parameter of type 'MutationData | Promise<MutationData | undefined> | MutatorCallback<MutationData>'.
      Type 'undefined' is not assignable to type 'MutationData | Promise<MutationData | undefined> | MutatorCallback<MutationData>'.ts(2769)

image

If I could know the type of Data at this point, I can avoid attaching any by comparing the type of Data with MutationData at runtime.

Do you know how to solve this?
I'm waiting for any comments and advices.

Copy link
Author

@Key5n Key5n Jun 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My idea for how to solve this problem.

  1. use any
  2. tell the callback function about the type of Data and compare it like the following code
// this is invalid code, just for sharing my idea
if (typeof Data === typeof MutationData){
    
} else {
  ...
}
  1. soft type attaching like the following code
    const mutate = useCallback<SWRInfiniteKeyedMutator<Data[]>>(
      // eslint-disable-next-line func-names
      function <MutationData = Data[]>(
        data?:
          | Data
          | Promise<Data | undefined>
          | MutatorCallback<Data>
          | MutationData
          | Promise<MutationData | undefined>
          | MutatorCallback<MutationData>
          | undefined,
        opts?: boolean | SWRInfiniteMutatorOptions<Data[], MutationData>
      ): Promise<Data[] | MutationData | undefined> {

       // contents

        return arguments.length
          ? swr.mutate<Data[]>(
              data as any,
              {
                ...options,
                revalidate: shouldRevalidate
              } as any
            )
          : swr.mutate()
      },
      // swr.mutate is always the same reference
      // eslint-disable-next-line react-hooks/exhaustive-deps
      [infiniteKey, cache]
    )

// When passing as a boolean, it's explicitly used to disable/enable
// revalidation.
const options =
Expand Down
25 changes: 21 additions & 4 deletions src/infinite/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,27 @@ interface SWRInfiniteRevalidateFn<Data = any> {
(data: Data, key: Arguments): boolean
}

export type SWRInfiniteKeyedMutator<Data> = <MutationData = Data>(
data?: Data | Promise<Data | undefined> | MutatorCallback<Data>,
opts?: boolean | SWRInfiniteMutatorOptions<Data, MutationData>
) => Promise<Data | MutationData | undefined>
export type SWRInfiniteKeyedMutator<Data> = {
(
data?: Data | Promise<Data | undefined> | MutatorCallback<Data>,
opts?: boolean | SWRInfiniteMutatorOptions<Data, Data>
): Promise<Data | undefined>
<MutationData = Data>(
data:
| MutationData
| Promise<MutationData | undefined>
| MutatorCallback<MutationData>,
opts: Omit<
SWRInfiniteMutatorOptions<Data, MutationData>,
'populateCache'
> & {
populateCache: (
result: MutationData,
currentData: Data | undefined
) => Data
}
): Promise<Data | MutationData | undefined>
}

export interface SWRInfiniteMutatorOptions<Data = any, MutationData = Data>
extends Omit<MutatorOptions<Data, MutationData>, 'revalidate'> {
Expand Down
7 changes: 7 additions & 0 deletions test/type/mutate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,18 @@ export function useMutatorTypes() {

mutate(async () => '1')
mutate(async () => '1', { populateCache: false })
mutate(async () => 1, {
populateCache: (result: number) => `${result}`
})

// @ts-expect-error
mutate(async () => 1)
// @ts-expect-error
mutate(async () => 1, { populateCache: false })
// @ts-expect-error
mutate(async () => 1, {
populateCache: (result: number) => result
})
}

export function useConfigMutate() {
Expand Down
Loading