Skip to content
IC Reactor

Reactor

Defined in: core/src/reactor.ts:160

Reactor class for interacting with IC canisters.

This class provides core functionality for:

  • Direct agent calls using agent.call() and agent.query()
  • Query caching with TanStack Query integration
  • Method calls with result unwrapping

A = BaseActor

The actor service type

T extends TransformKey = "candid"

The type transformation to apply (default: candid = raw Candid types)

new Reactor<A, T>(config): Reactor<A, T>

Defined in: core/src/reactor.ts:176

ReactorParameters

Reactor<A, T>

readonly _actor: A

Defined in: core/src/reactor.ts:162

Phantom type brand for inference - never assigned at runtime


readonly transform: keyof TransformArgsRegistry<unknown> = "candid"

Defined in: core/src/reactor.ts:163


clientManager: ClientManager

Defined in: core/src/reactor.ts:164


name: string

Defined in: core/src/reactor.ts:165


canisterId: Principal

Defined in: core/src/reactor.ts:166


service: ServiceClass

Defined in: core/src/reactor.ts:167


pollingOptions: PollingOptions

Defined in: core/src/reactor.ts:168

get queryClient(): QueryClient

Defined in: core/src/reactor.ts:993

Get the query client from clientManager. This is the recommended way to access the query client for direct queries.

QueryClient


get agent(): HttpAgent

Defined in: core/src/reactor.ts:1001

Get the agent from clientManager. This is the recommended way to access the agent for direct calls.

HttpAgent

setCanisterId(canisterId): void

Defined in: core/src/reactor.ts:255

Set the canister ID for this reactor. Useful for dynamically switching between canisters of the same type (e.g., multiple ICRC tokens).

CanisterId

The new canister ID (as string or Principal)

void

// Switch to a different ledger canister
ledgerReactor.setCanisterId("ryjl3-tyaaa-aaaaa-aaaba-cai")
// Then use queries/mutations as normal
const { data } = icrc1NameQuery.useQuery()

setCanisterName(name): void

Defined in: core/src/reactor.ts:276

Set the canister name for this reactor. Useful for dynamically switching between canisters of the same type (e.g., multiple ICRC tokens).

string

The new canister name

void

// Switch to a different ledger canister
ledgerReactor.setCanisterName("icrc1")
// Then use queries/mutations as normal
const { data } = icrc1NameQuery.useQuery()

forCanister(canisterId): this

Defined in: core/src/reactor.ts:329

A reactor of the same class for another canister of the same interface, such as another ICRC ledger. It shares this reactor’s ClientManager (so its agent, identity and QueryClient), name and polling options. It starts from the interface, transform and validators this reactor has when it is made. Only the canister differs.

The same canister id always gives the same reactor, so it is safe to call during render and as a dependency of useMemo. The siblings of a reactor share that memo: a.forCanister(x).forCanister(y) is a.forCanister(y). The result is a separate reactor even for this reactor’s own canister, and it keeps its canister when this one’s setCanisterId moves.

Use one sibling per canister instead of retargeting a shared reactor with setCanisterId. Their query keys start with their own canister, so several tokens’ queries live side by side in the cache, and no call or retry of one is sent to another. A mutation invalidates its own canister’s queries. Never setCanisterId a sibling: ask for the other canister’s instead.

A subclass whose constructor takes options of its own passes them on by overriding the protected siblingParameters(canisterId). Its constructor has to build the reactor for the canisterId it is given: one that sets a canister of its own makes this throw rather than hand out a reactor for the wrong canister.

CanisterId

The other canister, as text or a Principal.

this

const ledger = new DisplayReactor<Ledger>({
clientManager,
idlFactory,
name: "ledger",
canisterId: "ryjl3-tyaaa-aaaaa-aaaba-cai", // ICP
})
// ckBTC, through the same agent, cache and interface
const ckbtc = ledger.forCanister("mxzaz-hqaaa-aaaar-qaada-cai")
const symbol = await ckbtc.fetchQuery({ functionName: "icrc1_symbol" })
// In React: one set of hooks per token, memoized by its canister
const hooks = useMemo(
() => createActorHooks(ledger.forCanister(tokenId)),
[tokenId]
)

getServiceInterface(): ServiceClass

Defined in: core/src/reactor.ts:397

Get the service interface (IDL.ServiceClass) for this reactor. Useful for introspection and codec generation.

ServiceClass

The service interface


isQueryMethod<M>(methodName): boolean

Defined in: core/src/reactor.ts:423

Check if a method is a query method (query or composite_query).

M extends string

M

boolean


generateQueryKey<M>(params, callConfig?): readonly unknown[]

Defined in: core/src/reactor.ts:465

M extends string

ReactorQueryParams<A, M, T>

CallConfig

readonly unknown[]


getQueryRetry<M>(functionName, queryKey): ((failureCount, error) => boolean) | undefined

Defined in: core/src/reactor.ts:590

The retry a TanStack query of functionName, cached under queryKey, runs with when the query sets no retry of its own.

For a query method it is undefined, and the QueryClient’s retry defaults apply as usual.

An update method runs on the canister again each time the query function runs, as a new call the IC cannot tell from a retry, so a retry after a lost response executed the update a second time. For one it is a function that retries only a failure proving the canister never ran the call, a SysTransient rejection (see isRetryableUpdateError), and only as often as the QueryClient’s retry default for queryKey would: its setQueryDefaults or defaultOptions.queries, or TanStack Query’s own three retries in a browser when neither sets one. With the QueryClient defineReactor creates, that is reactorUpdateRetry, and with a default of retry: false nothing is retried. A transport failure, a SysUnknown rejection and an HTTP error are not retried. Refetches on mount, window focus, reconnect and invalidation still run the method again.

getQueryOptions, fetchQuery and the query hooks and factories of @ic-reactor/react apply it, and a retry the query sets itself wins over it.

M extends string

M

The method the query calls.

readonly unknown[]

The query’s key, which selects its QueryClient defaults.

((failureCount, error) => boolean) | undefined


getQueryOptions<M>(params): FetchQueryOptions<ReactorQueryData<ReactorReturnOk<A, M, T>>>

Defined in: core/src/reactor.ts:613

The key and function of a TanStack query of the method, for queryClient.fetchQuery, prefetchQuery or useQuery. For an update method they also hold the retry from getQueryRetry; spread a retry of your own after them to replace it.

M extends string

ReactorCallParams<A, M, T>

FetchQueryOptions<ReactorQueryData<ReactorReturnOk<A, M, T>>>


invalidateQueries<M>(params?, callConfig?): Promise<void>

Defined in: core/src/reactor.ts:677

Invalidate cached queries for this canister. This will mark matching queries as stale and trigger a refetch for any active queries.

It returns TanStack Query’s promise, which resolves once the active queries it matched have refetched. A refetch that fails does not reject it. Await it where the next step should read the refetched data, such as in a mutation’s onSuccess; an arrow that returns it, like onSuccess: () => reactor.invalidateQueries(...), keeps the mutation pending until those refetches finish.

M extends string

Partial<ReactorQueryParams<A, M, T>>

Optional parameters to filter the invalidation. Without a functionName, every query of the canister is invalidated.

CallConfig

Optional call configuration. Its canisterId selects which canister’s queries are invalidated, as it does for the calls.

Promise<void>

// Invalidate all queries for this canister, and wait for the active
// ones to refetch
await reactor.invalidateQueries()
// Invalidate only 'getUser' queries
reactor.invalidateQueries({ functionName: 'getUser' })
// Invalidate 'getUser' query for specific user
reactor.invalidateQueries({ functionName: 'getUser', args: ['user-1'] })
// Invalidate all queries of a canister reached through an override
reactor.invalidateQueries(undefined, { canisterId: otherLedgerId })

callMethod<M>(params): Promise<ReactorReturnOk<A, M, T>>

Defined in: core/src/reactor.ts:730

Call a canister method directly using agent.call() or agent.query(). This is the recommended approach for interacting with canisters.

M extends string

Omit<ReactorCallParams<A, M, T>, "queryKey">

Promise<ReactorReturnOk<A, M, T>>

// Query method
const result = await reactor.callMethod({
functionName: 'greet',
args: ['world'],
});
// Update method with options
const result = await reactor.callMethod({
functionName: 'transfer',
args: [{ to: principal, amount: 100n }],
callConfig: { effectiveCanisterId: principal },
});

fetchQuery<M>(params, options?): Promise<ReactorQueryData<ReactorReturnOk<A, M, T>>>

Defined in: core/src/reactor.ts:832

Returns the cached value for the call, or fetches and caches it when there is none. Like TanStack Query’s ensureQueryData, it is cache-first: a cached value is returned even when it is stale or was invalidated, and a staleTime in options has no effect. For a value the canister returns now, use callMethod, or queryClient.fetchQuery with the options from getQueryOptions inside ClientManager.fetchAcrossIdentitySwitch, which keeps a sign-in or sign-out from resolving it with the previous principal’s cached data.

A sign-in or sign-out while the fetch is in flight cancels it, so the previous identity’s answer is never cached. The fetch then runs again for the identity installed now and resolves with that answer, rather than rejecting with TanStack’s CancelledError; see ClientManager.fetchAcrossIdentitySwitch.

M extends string

ReactorCallParams<A, M, T>

Omit<FetchQueryOptions<ReactorQueryData<ReactorReturnOk<A, M, T>>, ReactorReturnErr<A, M, T>, ReactorQueryData<ReactorReturnOk<A, M, T>>, readonly unknown[], never>, "queryKey" | "queryFn">

Further TanStack Query options for the fetch, such as retry, networkMode or meta. The query key and function always come from params. The query factories pass their config’s options through here, so a subclass that overrides this method still sees their fetches. Without a retry here, an update method retries as getQueryRetry says.

Promise<ReactorQueryData<ReactorReturnOk<A, M, T>>>


getQueryData<M>(params, callConfig?): ReactorQueryData<ReactorReturnOk<A, M, T>> | undefined

Defined in: core/src/reactor.ts:870

Get the current data from the cache without fetching.

M extends string

ReactorQueryParams<A, M, T>

CallConfig

ReactorQueryData<ReactorReturnOk<A, M, T>> | undefined


subnetId(): Promise<Principal>

Defined in: core/src/reactor.ts:973

Get the subnet ID for this canister.

Promise<Principal>


subnetState(options): Promise<ReadStateResponse>

Defined in: core/src/reactor.ts:980

Get the subnet state for this canister.

ReadStateOptions

Promise<ReadStateResponse>