import { useMemo, useCallback } from "react" import { useMutation, UseMutationOptions, UseMutationResult, QueryKey, } from "@tanstack/react-query" import { Reactor, ReactorArgs, ReactorReturnOk, FunctionName, TransformKey, ReactorReturnErr, isCanisterError, CanisterError, ErrResult, ActorMethodReturnType, TransformReturnRegistry, } from "@ic-reactor/core" import { CallConfig } from "@icp-sdk/core/agent" export interface UseActorMutationParameters< A, M extends FunctionName, T extends TransformKey = "candid", > extends Omit< UseMutationOptions< ReactorReturnOk, ReactorReturnErr, ReactorArgs >, "mutationFn" > { reactor: Reactor functionName: M callConfig?: CallConfig invalidateQueries?: QueryKey[] /** * Callback for canister-level business logic errors. * Called when the canister returns a Result { Err: E } variant. * Separate from `onError`, which fires for all errors including network failures. */ onCanisterError?: ( error: CanisterError< TransformReturnRegistry>>[T] >, variables: ReactorArgs ) => void } export type UseActorMutationConfig< A, M extends FunctionName, T extends TransformKey = "candid", > = Omit, "reactor"> export type UseActorMutationResult< A, M extends FunctionName, T extends TransformKey = "candid", > = UseMutationResult< ReactorReturnOk, ReactorReturnErr, ReactorArgs > /** * Hook for executing mutation calls on a canister. * * @example * const { mutate, isPending } = useActorMutation({ * reactor, * functionName: "transfer", * onSuccess: () => console.log("Success!"), * onCanisterError: (err) => console.error("Canister Err:", err.code), * }) */ export const useActorMutation = < A, M extends FunctionName, T extends TransformKey = "candid", >({ reactor, functionName, invalidateQueries, onSuccess, onError, onCanisterError, callConfig, ...options }: UseActorMutationParameters): UseActorMutationResult => { const mutationFn = useCallback( async (args: ReactorArgs) => reactor.callMethod({ functionName, callConfig, args }), [reactor, functionName, callConfig] ) const handleSuccess = useCallback( async ( ...params: Parameters< NonNullable< UseMutationOptions< ReactorReturnOk, ReactorReturnErr, ReactorArgs >["onSuccess"] > > ) => { if (invalidateQueries) { await Promise.all( invalidateQueries.map((queryKey) => reactor.queryClient.invalidateQueries({ queryKey }) ) ) } await onSuccess?.(...params) }, [reactor, invalidateQueries, onSuccess] ) const handleError = useCallback( ( error: ReactorReturnErr, variables: ReactorArgs, context: unknown, mutation: unknown ) => { if (isCanisterError(error)) { onCanisterError?.(error as any, variables) } onError?.(error, variables, context as any, mutation as any) }, [onCanisterError, onError] ) const mutationOptions = useMemo( () => ({ ...options, mutationFn, onSuccess: handleSuccess, onError: handleError, }), // eslint-disable-next-line react-hooks/exhaustive-deps [mutationFn, handleSuccess, handleError] ) return useMutation(mutationOptions, reactor.queryClient) }