Skip to content
IC Reactor

useActorMethod

useActorMethod is a unified hook for calling canister methods that automatically handles both query and mutation methods based on the Candid interface. It is the bound version of useReactorMethod and is generated per-canister using createActorHooks.

  • Automatic Detection: Determines query vs mutation based on Candid annotations (query, composite_query).
  • Unified Interface: Same hook structure regardless of method type.
  • TypeScript Support: Full type safety for arguments and return types.
  • Cache Invalidation: Built-in support for invalidating queries after successful mutations.
import { createActorHooks } from "@ic-reactor/react"
import { backend } from "./reactor"
const { useActorMethod } = createActorHooks(backend)

For query methods, the hook behaves like useActorQuery and fetches data on mount.

import { useActorMethod } from "./reactor"
function Balance({ owner }: { owner: string }) {
const { data, isLoading, error } = useActorMethod({
functionName: "icrc1_balance_of",
args: [{ owner }],
})
if (isLoading) return <div>Loading...</div>
return <div>Balance: {data?.toString()}</div>
}

For update methods, the hook behaves like useActorMutation. Data is only fetched when you call the call function.

import { useActorMethod } from "./reactor"
function Transfer() {
const { call, isPending, data } = useActorMethod({
functionName: "icrc1_transfer",
// Optional: refetch every icrc1_balance_of query after a successful call
invalidateQueries: [{ functionName: "icrc1_balance_of" }],
// `call` never rejects: a failed call resolves `undefined` and reports
// its error here, so react to the outcome in the callbacks
onSuccess: () => alert("Transfer successful!"),
onError: (error) => alert(`Transfer failed: ${error.message}`),
})
const handleTransfer = () => {
void call([{ to: { owner: recipient }, amount: "100" }])
}
return (
<button onClick={handleTransfer} disabled={isPending}>
{isPending ? "Transferring..." : "Transfer"}
</button>
)
}

An invalidateQueries entry is a { functionName, args? } method of the hook’s reactor, a query object or query factory, or a query key. Without args, a method entry covers every query of that method, whatever its arguments. Query keys start with the canister ID, so a hand-written key such as ["icrc1_balance_of"] matches no query; build one with the reactor’s generateQueryKey or a query object’s getQueryKey().

UseActorMethodParameters extends react-query’s QueryObserverOptions, giving you access to all standard react-query options plus our custom actor options.

Option Type Description
functionName M Required. The method name to call on the canister.
args ReactorArgs<A, M, T> Arguments to pass to the method.
callConfig CallConfig Agent call configuration (e.g., effectiveCanisterId).
queryKey QueryKey Custom query key for queries.
onSuccess (data) => void Callback when the method call succeeds.
onError (error) => void Callback when the method call fails.
invalidateQueries InvalidationTarget[] For mutations: queries to invalidate after success.

For an update method, the invalidateQueries invalidation is awaited before onSuccess runs, as in useActorMutation and createMutation. onSuccess sees the refetched data, and call() resolves once the invalidated queries in use have refetched.

All react-query options are available:

Option Type Default Description
enabled boolean true For queries: whether to auto-fetch.
staleTime number 0 How long data stays fresh (ms).
gcTime number 300000 Time before unused data is garbage collected (ms).
refetchInterval number | false false Polling interval in ms.
refetchOnWindowFocus boolean true Refetch when window regains focus.
refetchOnMount boolean true Refetch when component mounts.
refetchOnReconnect boolean true Refetch when network reconnects.
retry number | boolean | function see below Number of retry attempts, or a predicate.
retryDelay number | function exponential Delay between retries.
networkMode string "online" Network mode ("online", "always", "offlineFirst").
placeholderData TData | function - Placeholder data while loading.

retry, retryDelay, networkMode and meta decide how a call runs, so they apply to both kinds of method: to a query method’s fetches and call(), and to an update method’s call(), which runs as a mutation. Unset, each follows the QueryClient’s defaults for that kind of call:

  • A query method retries as the QueryClient’s query retry says (reactorRetry with defineReactor).
  • An update method retries as the QueryClient’s mutation defaults say, which retry nothing unless the app set mutations.retry. The query default does not apply: each attempt of an update is a new call the canister runs, so a retry after a lost response would execute it twice. Set retry here only for an update that is safe to repeat, or use reactorUpdateRetry, which retries only a SysTransient rejection, the one failure that proves the call never ran.
import { reactorUpdateRetry } from "@ic-reactor/react"
import { useActorMethod } from "./reactor"
function SendButton() {
const { call } = useActorMethod({
functionName: "icrc1_transfer",
// Retry only when the canister provably never ran the call.
retry: reactorUpdateRetry,
// A local replica answers while the browser reports itself offline.
networkMode: "always",
meta: { errorMessage: "Could not send the transfer" },
})
return (
<button onClick={() => call([{ to: { owner: recipient }, amount }])}>
Send
</button>
)
}
Property Type Description
data ReactorQueryData<ReactorReturnOk<A, M, T>> | undefined The returned data. A method’s undefined result is null, as in useActorQuery, so undefined means that no call has settled yet, that the last call failed, or that an update call is running. A query keeps its earlier data when a refetch fails.
call (args?) => Promise<data | undefined> Triggers the call. It never rejects: a failed call resolves undefined and reports its error to onError.
isLoading boolean Whether the method is currently executing (alias for isPending).
isPending boolean Whether the method is currently executing.
isError boolean Whether the last call resulted in an error.
isSuccess boolean Whether the method has completed successfully.
error ReactorReturnErr<A, M, T> | null The error object if one occurred.
isQuery boolean true if it’s a query method, false otherwise.
functionType FunctionType "query" or "update". Candid composite_query methods report "query".
reset () => void Resets the state. For a query, data goes back to initialData if one was given and is cleared otherwise, and an enabled hook refetches.
refetch () => Promise For queries: manually refetches the data. Resolves undefined when the refetch fails, whose error goes to onError; data keeps the last answer.
queryResult UseQueryResult The raw result if it’s a query.
mutationResult UseMutationResult The raw result if it’s a mutation.

For a query method, call(args) fetches those args from the canister even when they are cached (or joins a fetch of them already in flight), and call() and refetch() refetch the hook’s own args. A sign-in or sign-out that switches the principal while one of them is in flight cancels its fetch, so the previous principal’s answer is never cached, and the fetch runs again for the principal signed in. The promise resolves with that principal’s answer, or undefined when that fetch fails, never with the previous principal’s data or TanStack’s CancelledError, and onSuccess or onError hears of it once. If the principal switches again during each of the three runs that follow, the call reports a CallError to onError and resolves undefined rather than an answer that may be a previous principal’s. reactor.fetchQuery() and the query factories’ fetch() behave the same; see fetchAcrossIdentitySwitch.

An update method’s call() runs as a mutation, which a sign-in or sign-out does not cancel.