diff --git a/src/lib/queries/mutationOptions.ts b/src/lib/queries/mutationOptions.ts new file mode 100644 index 0000000..327bd2e --- /dev/null +++ b/src/lib/queries/mutationOptions.ts @@ -0,0 +1,71 @@ +import type { UseMutationOptions } from "@tanstack/react-query" +import type { Observable } from "rxjs" + +/** + * Resolves the public `mutationFn` option — an Observable or a function + * returning one — to its source Observable for the given variables. + */ +export function resolveMutationFnSource( + mutationFn: + | ((variables: TVariables) => Observable) + | Observable, + variables: TVariables, +) { + return typeof mutationFn === "function" ? mutationFn(variables) : mutationFn +} + +type MutationCallbacks = Pick< + UseMutationOptions, + "onMutate" | "onSuccess" | "onError" | "onSettled" +> + +/** + * Same callbacks, but keyed as required so that spreading the adapted + * callbacks after `...options` overrides (rather than unions with) the + * raw-variables callbacks still present in `options`. + */ +type AdaptedMutationCallbacks = { + [K in "onMutate" | "onSuccess" | "onError" | "onSettled"]: + | MutationCallbacks< + TData, + TError, + { variables: TVariables }, + TOnMutateResult + >[K] + | undefined +} + +/** + * Hooks built on top of `useMutation$` (`useSwitchMutation$`, + * `useConcatMutation$`) run their inner mutation with the user variables + * wrapped in an envelope (`{ variables, ... }`). This adapts the user-facing + * callbacks, which expect the raw variables, to that envelope. + */ +export function adaptCallbacksToWrappedVariables< + TData, + TError, + TVariables, + TOnMutateResult, +>({ + onMutate, + onSuccess, + onError, + onSettled, +}: MutationCallbacks< + TData, + TError, + TVariables, + TOnMutateResult +>): AdaptedMutationCallbacks { + return { + onMutate: onMutate + ? ({ variables }, ...rest) => onMutate(variables, ...rest) + : undefined, + onSuccess: (data, { variables }, ...rest) => + onSuccess?.(data, variables, ...rest), + onError: (error, { variables }, ...rest) => + onError?.(error, variables, ...rest), + onSettled: (data, error, { variables }, ...rest) => + onSettled?.(data, error, variables, ...rest), + } +} diff --git a/src/lib/queries/useConcatMutation$.ts b/src/lib/queries/useConcatMutation$.ts index 6bc337e..2a34ba3 100644 --- a/src/lib/queries/useConcatMutation$.ts +++ b/src/lib/queries/useConcatMutation$.ts @@ -13,6 +13,10 @@ import { type Subject, switchMap, } from "rxjs" +import { + adaptCallbacksToWrappedVariables, + resolveMutationFnSource, +} from "./mutationOptions" import { type UseMutation$Options, useMutation$ } from "./useMutation$" export function useConcatMutation$< @@ -21,10 +25,7 @@ export function useConcatMutation$< TVariables = void, TContext = unknown, >( - { - onMutate, - ...options - }: UseMutation$Options & { + options: UseMutation$Options & { mutationKey: MutationKey }, queryClient?: QueryClient, @@ -41,23 +42,9 @@ export function useConcatMutation$< >( { ...options, - onMutate: onMutate - ? ({ variables }, ...rest) => onMutate(variables, ...rest) - : undefined, - onSuccess(data, { variables }, ...rest) { - return options.onSuccess?.(data, variables, ...rest) - }, - onError(error, { variables }, ...rest) { - return options.onError?.(error, variables, ...rest) - }, - onSettled(data, error, { variables }, ...rest) { - return options.onSettled?.(data, error, variables, ...rest) - }, + ...adaptCallbacksToWrappedVariables(options), mutationFn: ({ ready$, variables }) => { - const source = - typeof options.mutationFn === "function" - ? options.mutationFn(variables) - : options.mutationFn + const source = resolveMutationFnSource(options.mutationFn, variables) return ready$.pipe( filter((isReady) => isReady), diff --git a/src/lib/queries/useMutation$.ts b/src/lib/queries/useMutation$.ts index 334200c..1c98c17 100644 --- a/src/lib/queries/useMutation$.ts +++ b/src/lib/queries/useMutation$.ts @@ -8,6 +8,7 @@ import { import { useEffect } from "react" import { BehaviorSubject, type Observable, take } from "rxjs" import { useConstant } from "../utils/react/useConstant" +import { resolveMutationFnSource } from "./mutationOptions" export type UseMutation$Options< TData = unknown, @@ -53,10 +54,7 @@ export function useMutation$< let lastData: { value: TData } | undefined return new Promise((resolve, reject) => { - const source = - typeof options.mutationFn === "function" - ? options.mutationFn(variables) - : options.mutationFn + const source = resolveMutationFnSource(options.mutationFn, variables) source.pipe(take(1)).subscribe({ next: (data) => { diff --git a/src/lib/queries/useSwitchMutation$.ts b/src/lib/queries/useSwitchMutation$.ts index e67eb95..1397d97 100644 --- a/src/lib/queries/useSwitchMutation$.ts +++ b/src/lib/queries/useSwitchMutation$.ts @@ -9,6 +9,10 @@ import { tap, } from "rxjs" import { useRefOnce } from "../utils" +import { + adaptCallbacksToWrappedVariables, + resolveMutationFnSource, +} from "./mutationOptions" import { type UseMutation$Options, useMutation$ } from "./useMutation$" export class SwitchMutationCancelError extends Error { @@ -26,9 +30,6 @@ export function useSwitchMutation$< >( { mutationFn, - onMutate, - onError, - onSettled, ...options }: UseMutation$Options, queryClient?: QueryClient, @@ -56,10 +57,7 @@ export function useSwitchMutation$< throw new SwitchMutationCancelError() } - const source = - typeof mutationFn === "function" - ? mutationFn(variables) - : mutationFn + const source = resolveMutationFnSource(mutationFn, variables) /** * `defaultIfEmpty` must sit on the source itself: the abort stream @@ -79,20 +77,7 @@ export function useSwitchMutation$< }, [mutationFn], ), - onMutate: onMutate - ? ({ variables }, ...rest) => { - return onMutate(variables, ...rest) - } - : undefined, - onSuccess: (data, { variables }, ...rest) => { - return options.onSuccess?.(data, variables, ...rest) - }, - onError: (error, { variables }, ...rest) => { - return onError?.(error, variables, ...rest) - }, - onSettled: (data, error, { variables }, ...rest) => { - return onSettled?.(data, error, variables, ...rest) - }, + ...adaptCallbacksToWrappedVariables(options), }, queryClient, )