useSWRMutation trigger is uncallable when wrapped in a generic function
Bug report
Description / Observed Behavior
When wrapping useSWRMutation in a generic helper function, the trigger return type becomes an uncallable union of TriggerWithoutArgs | TriggerWithOptionsArgs | TriggerWithArgs.
TypeScript error:
TS2349: This expression is not callable.
Each member of the union type
'TriggerWithoutArgs<Data, any, Key, Args> | TriggerWithOptionsArgs<Data, any, Key, Args> | TriggerWithArgs<...>'
has signatures, but none of those signatures are compatible with each other.The conditional type that determines which trigger interface to use:
trigger: [ExtraArg] extends [never]
? TriggerWithoutArgs<...>
: IsUndefinedIncluded<ExtraArg> extends true
? TriggerWithOptionsArgs<...>
: TriggerWithArgs<...>cannot be evaluated when ExtraArg is a generic type parameter (not yet resolved). TypeScript defers the conditional type rather than resolving it to a single branch. The deferred type is opaque and uncallable because TS cannot determine which of the three incompatible call signatures applies.
Note: the [ExtraArg] extends [never] wrapping correctly prevents union distribution — this is a deferred conditional type issue, not a distributive conditional type issue.
Expected Behavior
trigger should be callable when useSWRMutation is used inside a generic wrapper function.
Repro Steps / Code Example
import useSWRMutation from 'swr/mutation'
import type { MutationFetcher } from 'swr/mutation'
export const useMutationFetch = <
Key extends string = string,
Data = unknown,
Args = unknown,
>(
key: Key,
fetcher: MutationFetcher<Data, Key, Args>
) => {
const { trigger } = useSWRMutation(key, fetcher)
// TS2349: This expression is not callable.
trigger({ someArg: 'value' })
}Currently requires @ts-expect-error to work around.
Additional Context
SWR version: 2.2.4 (also reproduced on 2.4.1)
The three-interface split was introduced in https://github.com/vercel/swr/pull/2604.
Related issues:
- https://github.com/vercel/swr/issues/2755 — trigger types breaking in custom hooks (fixed by exporting types, but the underlying deferred conditional issue was not addressed)
- https://github.com/vercel/swr/issues/4164 — difficulty wrapping SWR hooks with type safety
- https://github.com/vercel/swr/issues/2126 — generic fetcher inference issues
Proposed Fix
Collapse the three separate interfaces (TriggerWithoutArgs, TriggerWithOptionsArgs, TriggerWithArgs) into a single interface using conditional rest parameters. This pushes the conditional into the parameter position, where TypeScript handles deferral more gracefully — all branches share one callable signature with the same return type.
interface Trigger<Data, Error, SWRMutationKey extends Key, ExtraArg> {
// throwOnError: true
<SWRData = Data>(
...args: [ExtraArg] extends [never]
? [options?: SWRMutationConfiguration<SWRData, Error, SWRMutationKey, ExtraArg> & { throwOnError: true }]
: IsUndefinedIncluded<ExtraArg> extends true
? [extraArgument?: ExtraArg, options?: SWRMutationConfiguration<SWRData, Error, SWRMutationKey, ExtraArg> & { throwOnError: true }]
: [extraArgument: ExtraArg, options?: SWRMutationConfiguration<SWRData, Error, SWRMutationKey, ExtraArg> & { throwOnError: true }]
): Promise<SWRData>
// throwOnError: false | undefined (default)
<SWRData = Data>(
...args: [ExtraArg] extends [never]
? [options?: SWRMutationConfiguration<SWRData, Error, SWRMutationKey, ExtraArg> & { throwOnError?: false }]
: IsUndefinedIncluded<ExtraArg> extends true
? [extraArgument?: ExtraArg, options?: SWRMutationConfiguration<SWRData, Error, SWRMutationKey, ExtraArg> & { throwOnError?: false }]
: [extraArgument: ExtraArg, options?: SWRMutationConfiguration<SWRData, Error, SWRMutationKey, ExtraArg> & { throwOnError?: false }]
): Promise<SWRData | undefined>
}By keeping all overloads within one interface, the trigger property type is always a single interface (not a conditional selecting between three), so it remains callable even when ExtraArg is an unresolved generic.
Source: vercel/swr