#4234·swr

useSWRMutation trigger is uncallable when wrapped in a generic function

Author: quicksilverselCreated Mar 25, 2026Updated Mar 25, 2026

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:

typescript
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

typescript
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:

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.

typescript
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.