useActorMutation
useActorMutation is a React hook for calling update methods on Internet Computer canisters. It wraps TanStack Query’s useMutation with canister-specific functionality.
Import
Section titled “Import”import { createActorHooks } from "@ic-reactor/react"import { backend } from "./reactor"
const { useActorMutation } = createActorHooks(backend)import { useActorMutation } from "./reactor"
function CreatePost() { const { mutate, isPending } = useActorMutation({ functionName: "create_post", })
return ( <button onClick={() => mutate([{ title: "Hello", content: "World" }])} disabled={isPending} > {isPending ? "Creating..." : "Create Post"} </button> )}Options
Section titled “Options”UseActorMutationParameters extends react-query’s UseMutationOptions, giving you access to all standard react-query mutation options plus our custom actor options.
Required Options
Section titled “Required Options”| Option | Type | Description |
|---|---|---|
functionName |
string |
The canister method to call |
Optional Options
Section titled “Optional Options”| Option | Type | Default | Description |
|---|---|---|---|
callConfig |
CallConfig |
- | IC agent call configuration |
invalidateQueries |
InvalidationTarget[] |
- | Queries to invalidate on success, before onSuccess |
onCanisterError |
function |
- | Called only for Result { Err } variants (canister logic errors) |
retry |
number | boolean | function |
0 |
Number of retry attempts, or a predicate such as reactorUpdateRetry |
retryDelay |
number | function |
- | Delay between retries |
onMutate |
function |
- | Called before mutation |
onSuccess |
function |
- | Called on successful mutation |
onError |
function |
- | Called on ALL errors (canister, network, agent) |
onSettled |
function |
- | Called after success or error |
Callback Signatures
Section titled “Callback Signatures”onMutate: (variables: Args) => Promise<Context> | Context
onSuccess: (data: Data, variables: Args, context: Context) => Promise<void> | void
onError: (error: Error, variables: Args, context: Context) => Promise<void> | void
onSettled: (data: Data | undefined, error: Error | null, variables: Args, context: Context) => Promise<void> | voidReturn Value
Section titled “Return Value”Returns a TanStack Query mutation result object:
| Property | Type | Description |
|---|---|---|
mutate |
function |
Trigger the mutation |
mutateAsync |
function |
Trigger and return a promise |
data |
TData | undefined |
Last successful result |
error |
Error | null |
Error if mutation failed |
status |
'idle' | 'pending' | 'error' | 'success' |
Mutation status |
isIdle |
boolean |
True if mutation hasn’t run |
isPending |
boolean |
True if mutation is running |
isError |
boolean |
True if mutation errored |
isSuccess |
boolean |
True if mutation succeeded |
reset |
function |
Reset mutation state |
variables |
Args |
Variables passed to mutate |
Examples
Section titled “Examples”Basic Mutation
Section titled “Basic Mutation”import { useActorMutation } from "./reactor"
function CreatePostButton() { const { mutate, isPending, isSuccess } = useActorMutation({ functionName: "create_post", })
const handleCreate = () => { mutate([{ title: "Hello", content: "World" }]) }
return ( <button onClick={handleCreate} disabled={isPending}> {isSuccess ? "Created" : "Create post"} </button> )}With Callbacks
Section titled “With Callbacks”import { useActorMutation } from "./reactor"// `toast` is your notification helper, such as sonner's
function useTransfer() { return useActorMutation({ functionName: "icrc1_transfer", onMutate: (args) => { console.log("Starting transfer:", args) return { startTime: Date.now() } }, onSuccess: (data, args, context) => { console.log("Transfer completed in", Date.now() - context.startTime, "ms") toast.success("Transfer successful!") }, onError: (error, args, context) => { console.error("Transfer failed:", error) toast.error("Transfer failed: " + error.message) }, onSettled: () => { console.log("Transfer finished (success or error)") }, })}Invalidate Queries After Mutation
Section titled “Invalidate Queries After Mutation”import { useActorMutation } from "./reactor"import { getPost } from "../queries" // createQueryFactory(backend, ...)
function useCreatePost() { return useActorMutation({ functionName: "create_post", invalidateQueries: [ { functionName: "get_posts" }, // every get_posts query, whatever its args { functionName: "get_posts_count" }, getPost, // every query this factory returned ], })}Each entry is a { functionName, args? } method of the reactor, type-checked
against the canister, a query object or query factory, or a query key. The
invalidation is awaited before onSuccess runs, so the mutation stays pending
until the invalidated queries in use have refetched. undefined entries are
skipped.
Using mutateAsync
Section titled “Using mutateAsync”import { useActorMutation } from "./reactor"
// `toast` is your notification helper, such as sonner's
function TransferButton() { const { mutateAsync, isPending } = useActorMutation({ functionName: "icrc1_transfer", })
const handleTransfer = async () => { try { const blockIndex = await mutateAsync([ { to: { owner: recipient }, amount }, ]) toast.success(`Transferred in block ${blockIndex}!`) navigate("/transactions") } catch (error) { toast.error("Transfer failed") } }
return ( <button onClick={handleTransfer} disabled={isPending}> Send </button> )}Form Integration
Section titled “Form Integration”import { useState } from "react"import { useActorMutation } from "./reactor"
function CreateUserForm() { const [name, setName] = useState("") const [email, setEmail] = useState("")
const { mutate, isPending, isError, error, reset } = useActorMutation({ functionName: "create_user", onSuccess: () => { setName("") setEmail("") }, })
const handleSubmit = (e: React.FormEvent) => { e.preventDefault() mutate([{ name, email }]) }
return ( <form onSubmit={handleSubmit}> <input value={name} onChange={(e) => setName(e.target.value)} disabled={isPending} placeholder="Name" /> <input value={email} onChange={(e) => setEmail(e.target.value)} disabled={isPending} placeholder="Email" />
{isError && ( <div className="error"> {error.message} <button type="button" onClick={reset}> Dismiss </button> </div> )}
<button type="submit" disabled={isPending}> {isPending ? "Creating..." : "Create User"} </button> </form> )}Optimistic Updates
Section titled “Optimistic Updates”import { createQueryFactory } from "@ic-reactor/react"import { backend, useActorMutation } from "../reactor"
const getPost = createQueryFactory(backend, { functionName: "get_post" })
function LikeButton({ postId }: { postId: string }) { const { mutate } = useActorMutation({ functionName: "like_post", // Cancel outgoing refetches, write the new value, keep a rollback onMutate: ([postId]) => getPost([postId]).optimisticUpdate((post) => ({ ...post, // A DisplayReactor caches a `nat` as text likes: (BigInt(post.likes) + 1n).toString(), isLiked: true, })), // Rollback on error onError: (_error, _args, update) => update?.rollback(), // Refetch to ensure consistency onSettled: (_data, _error, [postId]) => getPost([postId]).invalidate(), })
return <button onClick={() => mutate([postId])}>❤️ Like</button>}The updater is typed from the query and gets the value in the cache (before any
select); it is not called when nothing is cached. See
Optimistic Updates.
Canister Error Handling
Section titled “Canister Error Handling”Use onCanisterError to handle Result { Err } variants separately from network
or agent errors. This is cleaner than checking isCanisterError(error) inside
onError:
import { useActorMutation } from "./reactor"// `toast` is your notification helper, such as sonner's
function useTransfer() { return useActorMutation({ functionName: "icrc1_transfer", // Only fires for canister Result { Err } variants onCanisterError: (error) => { // A DisplayReactor gives a variant a `_type`; switch on it if (error.err._type === "InsufficientFunds") { toast.error("Not enough balance") } else if (error.err._type === "BadFee") { toast.error("Wrong transfer fee") } else { toast.error("Transaction failed: " + error.code) } }, // Fires for ALL errors: canister Err, network failures, agent errors onError: (error) => { console.error("Unexpected error", error) }, })}onCanisterError also receives the mutation variables as its second argument:
import { useActorMutation } from "./reactor"
function useTransfer() { return useActorMutation({ functionName: "icrc1_transfer", onCanisterError: (error, [{ to, amount }]) => { console.error( `Transfer of ${amount} to ${to.owner} failed: ${error.code}` ) }, })}Per-Mutation Callbacks
Section titled “Per-Mutation Callbacks”import { useActorMutation } from "./reactor"
function PublishButton() { const { mutate } = useActorMutation({ functionName: "create_post", onSuccess: () => { // This runs first (from hook config) console.log("Post created!") }, })
// In an event handler: const handlePublish = () => mutate([postData], { onSuccess: (post) => { // This runs second (from mutate call) navigate(`/posts/${post.id}`) }, })
return <button onClick={handlePublish}>Publish</button>}With Loading State
Section titled “With Loading State”import { useEffect } from "react"import { useActorMutation } from "./reactor"
function TransferButton() { const { mutate, isPending, isSuccess, reset } = useActorMutation({ functionName: "icrc1_transfer", })
useEffect(() => { if (isSuccess) { const timer = setTimeout(reset, 3000) return () => clearTimeout(timer) } }, [isSuccess, reset])
return ( <button onClick={() => mutate([{ to: { owner: recipient }, amount }])} disabled={isPending} > {isPending && "Transferring..."} {isSuccess && "✓ Done!"} {!isPending && !isSuccess && "Transfer"} </button> )}Type Parameters
Section titled “Type Parameters”useActorMutation<M extends FunctionName<A>>( options: UseActorMutationParameters<A, M, T>)Types are automatically inferred from your actor interface.
See Also
Section titled “See Also”- Mutations Guide — In-depth guide to mutations
- useActorQuery — Query hook reference
- Query Caching — Cache invalidation patterns
- createActorHooks — Creating hooks