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.
Features
Section titled “Features”- 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
Section titled “Import”import { createActorHooks } from "@ic-reactor/react"import { backend } from "./reactor"
const { useActorMethod } = createActorHooks(backend)Query Method (Auto-fetches)
Section titled “Query Method (Auto-fetches)”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>}Mutation Method (Manual Call)
Section titled “Mutation Method (Manual Call)”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().
Options
Section titled “Options”UseActorMethodParameters extends react-query’s QueryObserverOptions, giving you access to all standard react-query options plus our custom actor options.
Custom Options
Section titled “Custom 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.
Inherited from React Query
Section titled “Inherited from React Query”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
retrysays (reactorRetrywithdefineReactor). - 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. Setretryhere only for an update that is safe to repeat, or usereactorUpdateRetry, which retries only aSysTransientrejection, 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> )}Return Value
Section titled “Return Value”| 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. |
Across a sign-in or sign-out
Section titled “Across a sign-in or sign-out”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.
See Also
Section titled “See Also”- useReactorMethod — Raw version
- useActorQuery
- useActorMutation