Skip to content

Commit

Permalink
types: fix some mutation type issue (#2421)
Browse files Browse the repository at this point in the history
* types: fix some mutation type issue

close #2418 #2406 #2280

* chore: update

* fix lint
  • Loading branch information
promer94 committed Feb 11, 2023
1 parent f7004ac commit 21dffae
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 31 deletions.
2 changes: 1 addition & 1 deletion _internal/types.ts
Expand Up @@ -373,7 +373,7 @@ export interface ScopedMutator<Data = any> {
}

export type KeyedMutator<Data> = (
data?: Data | Promise<Data> | MutatorCallback<Data>,
data?: Data | Promise<Data | undefined> | MutatorCallback<Data>,
opts?: boolean | MutatorOptions<Data>
) => Promise<Data | undefined>

Expand Down
6 changes: 5 additions & 1 deletion infinite/index.ts
Expand Up @@ -197,7 +197,11 @@ export const infinite = (<Data, Error>(useSWRNext: SWRHook) =>
const mutate = useCallback(
// eslint-disable-next-line func-names
function (
data?: undefined | Data[] | Promise<Data[]> | MutatorCallback<Data[]>,
data?:
| undefined
| Data[]
| Promise<Data[] | undefined>
| MutatorCallback<Data[]>,
opts?: undefined | boolean | MutatorOptions<Data[]>
) {
// When passing as a boolean, it's explicitly used to disable/enable
Expand Down
47 changes: 36 additions & 11 deletions mutation/types.ts
@@ -1,4 +1,4 @@
import type { SWRResponse, Key, MutatorOptions } from 'swr'
import type { SWRResponse, Key } from 'swr'

type FetcherResponse<Data> = Data | Promise<Data>

Expand All @@ -22,29 +22,37 @@ export type SWRMutationConfiguration<
Data,
Error,
ExtraArg = any,
SWRMutationKey extends Key = Key
> = MutatorOptions<Data> & {
SWRMutationKey extends Key = Key,
SWRData = any
> = {
revalidate?: boolean
populateCache?:
| boolean
| ((result: Data, currentData: SWRData | undefined) => SWRData)
optimisticData?: SWRData | ((currentData?: SWRData) => SWRData)
rollbackOnError?: boolean | ((error: unknown) => boolean)
throwOnError?: boolean
fetcher?: MutationFetcher<Data, ExtraArg, SWRMutationKey>
onSuccess?: (
data: Data,
key: string,
config: Readonly<
SWRMutationConfiguration<Data, Error, ExtraArg, SWRMutationKey>
SWRMutationConfiguration<Data, Error, ExtraArg, SWRMutationKey, SWRData>
>
) => void
onError?: (
err: Error,
key: string,
config: Readonly<
SWRMutationConfiguration<Data, Error, ExtraArg, SWRMutationKey>
SWRMutationConfiguration<Data, Error, ExtraArg, SWRMutationKey, SWRData>
>
) => void
}

export interface SWRMutationResponse<
Data = any,
Error = any,
ExtraArg = any,
ExtraArg = never,
SWRMutationKey extends Key = Key
> extends Pick<SWRResponse<Data, Error>, 'data' | 'error'> {
/**
Expand All @@ -55,10 +63,27 @@ export interface SWRMutationResponse<
* Function to trigger the mutation. You can also pass an extra argument to
* the fetcher, and override the options for the mutation hook.
*/
trigger: (
extraArgument?: ExtraArg,
options?: SWRMutationConfiguration<Data, Error, ExtraArg, SWRMutationKey>
) => Promise<Data | undefined>
trigger: [ExtraArg] extends [never]
? <SWRData = Data>(
extraArgument?: null,
options?: SWRMutationConfiguration<
Data,
Error,
ExtraArg,
SWRMutationKey,
SWRData
>
) => Promise<Data | undefined>
: <SWRData = Data>(
extraArgument: ExtraArg,
options?: SWRMutationConfiguration<
Data,
Error,
ExtraArg,
SWRMutationKey,
SWRData
>
) => Promise<Data | undefined>
/**
* Function to reset the mutation state (`data`, `error`, and `isMutating`).
*/
Expand All @@ -69,7 +94,7 @@ export type SWRMutationHook = <
Data = any,
Error = any,
SWRMutationKey extends Key = Key,
ExtraArg = any
ExtraArg = never
>(
/**
* The key of the resource that will be mutated. It should be the same key
Expand Down
1 change: 0 additions & 1 deletion test/type/mutator.ts
@@ -1,6 +1,5 @@
import type { Equal, Expect } from '@type-challenges/utils'
import useSWR, { useSWRConfig } from 'swr'

import type {
MutatorFn,
Key,
Expand Down
65 changes: 59 additions & 6 deletions test/type/trigger.ts
@@ -1,5 +1,5 @@
import useSWRMutation from 'swr/mutation'

import useSWR from 'swr'
type ExpectType = <T>(value: T) => void
const expectType: ExpectType = () => {}

Expand All @@ -14,7 +14,7 @@ export function useExtraParam() {
expectType<string>(key)
})
useSWRMutation('/api/user', (_, opts) => {
expectType<Equal<typeof opts, Readonly<{ arg: any }>>>(true)
expectType<Equal<typeof opts, Readonly<{ arg: never }>>>(true)
})
}

Expand All @@ -25,8 +25,7 @@ export function useTrigger() {
)

// The argument of `trigger` should be number or undefined.
// TODO: handle the `undefined` cases.
expectType<Equal<Parameters<typeof trigger>[0], number | undefined>>(true)
expectType<Equal<Parameters<typeof trigger>[0], number>>(true)
expectType<Promise<string | undefined>>(trigger(1))

// Other return values
Expand All @@ -49,7 +48,61 @@ export function useTriggerWithParameter() {
)

// The argument of `trigger` should be number or undefined.
// TODO: handle the `undefined` cases.
expectType<Equal<Parameters<typeof trigger>[0], number | undefined>>(true)
expectType<Equal<Parameters<typeof trigger>[0], number>>(true)
expectType<Promise<string | undefined>>(trigger(1))
}

export function useTestSWRMutation() {
const { data } = useSWR('key', async () => {
return ['foo']
})
const { trigger } = useSWRMutation(
'key',
async (_, { arg }: { arg: 'foo' }) => {
return arg.toUpperCase()
}
)

const test = () => {
// @ts-expect-error `arg` should be 'foo'
trigger()

// @ts-expect-error `arg` should be 'foo'
trigger<typeof data>('bar', {
optimisticData: current => {
expectType<string[] | undefined>(current)
return []
},
populateCache: (added, current) => {
expectType<string>(added)
expectType<typeof data>(current)
return []
},
revalidate: false
})
}
test()
}

export function useTestSWRMutationWithSWRMutate() {
const { mutate } = useSWR('/some/key', () => {
return {
foo: 'bar'
}
})
const { trigger } = useSWRMutation('/some/key', () => {
return {
foo: 'foo'
}
})
const test = () => {
;async () => {
mutate(trigger(), {
optimisticData: {
foo: 'baz'
}
})
}
}
test()
}
31 changes: 20 additions & 11 deletions test/use-swr-remote-mutation.test.tsx
Expand Up @@ -56,7 +56,7 @@ describe('useSWR - remote mutation', () => {

it('should trigger request with the correct argument signature', async () => {
const key = createKey()
const fetcher = jest.fn(() => 'data')
const fetcher = jest.fn((_, __: { arg: string }) => 'data')

function Page() {
const { data, trigger } = useSWRMutation([key, 'arg0'], fetcher)
Expand Down Expand Up @@ -112,7 +112,7 @@ describe('useSWR - remote mutation', () => {
function Page() {
const { data, trigger } = useSWRMutation(key, () => 'data')
return (
<button onClick={() => trigger(undefined, { onSuccess })}>
<button onClick={() => trigger(null, { onSuccess })}>
{data || 'pending'}
</button>
)
Expand Down Expand Up @@ -235,7 +235,7 @@ describe('useSWR - remote mutation', () => {
function Page() {
const { data, error, trigger } = useSWRMutation(
key,
async (_, { arg: shouldReturnValue }) => {
async (_, { arg: shouldReturnValue }: { arg: boolean }) => {
await sleep(10)
if (shouldReturnValue) return 'data'
throw new Error('error')
Expand Down Expand Up @@ -344,7 +344,10 @@ describe('useSWR - remote mutation', () => {

function Page() {
const { data } = useSWR(key, () => 'data')
const { trigger } = useSWRMutation(key, (_, { arg }) => arg)
const { trigger } = useSWRMutation(
key,
(_, { arg }: { arg: string }) => arg
)
return (
<div onClick={() => trigger('updated!', { populateCache: true })}>
data:{data || 'none'}
Expand All @@ -369,7 +372,10 @@ describe('useSWR - remote mutation', () => {

function Page() {
const { data } = useSWR(key, () => 'data')
const { trigger } = useSWRMutation(key, (_, { arg }) => arg)
const { trigger } = useSWRMutation(
key,
(_, { arg }: { arg: string }) => arg
)
return (
<div
onClick={() =>
Expand Down Expand Up @@ -810,16 +816,19 @@ describe('useSWR - remote mutation', () => {
await sleep(10)
return ['foo']
})
const { trigger } = useSWRMutation(key, async (_, { arg }) => {
await sleep(20)
return arg.toUpperCase()
})
const { trigger } = useSWRMutation(
key,
async (_, { arg }: { arg: string }) => {
await sleep(20)
return arg.toUpperCase()
}
)

return (
<div>
<button
onClick={() =>
trigger('bar', {
trigger<typeof data>('bar', {
optimisticData: current => [...current, 'bar'],
populateCache: (added, current) => [...current, added],
revalidate: false
Expand Down Expand Up @@ -859,7 +868,7 @@ describe('useSWR - remote mutation', () => {
function Page() {
const { error, trigger } = useSWRMutation(
key,
async (_, { arg: shouldReturnValue }) => {
async (_, { arg: shouldReturnValue }: { arg: boolean }) => {
await sleep(10)
if (shouldReturnValue) return ['foo']
throw new Error('error')
Expand Down

0 comments on commit 21dffae

Please sign in to comment.