CandidReactor
CandidReactor extends the core Reactor class to support dynamic canister interaction. It can fetch or parse Candid at runtime, register methods dynamically, and then use all standard Reactor methods.
Overview
Section titled “Overview”The key insight is that CandidReactor injects methods into the service at runtime. After initialization (or method registration), all standard Reactor infrastructure works automatically:
callMethod()— Direct method callsfetchQuery()— TanStack Query cachinggetQueryOptions()— For React hooksinvalidateQueries()— Cache invalidation
const reactor = new CandidReactor({ canisterId: "ryjl3-tyaaa-aaaaa-aaaba-cai", clientManager,})
await reactor.initialize() // Fetch IDL from network
// Now it works like a regular Reactor!const name = await reactor.callMethod({ functionName: "icrc1_name" })Import
Section titled “Import”import { CandidReactor } from "@ic-reactor/candid"Constructor
Section titled “Constructor”new CandidReactor(config: CandidReactorParameters)Parameters
Section titled “Parameters”| Parameter | Type | Required | Description |
|---|---|---|---|
canisterId | CanisterId | Yes | The canister ID to interact with |
clientManager | ClientManager | Yes | Client manager from @ic-reactor/core |
candid | string | No | Candid service definition (avoids network fetch) |
idlFactory | InterfaceFactory | No | IDL factory (if already available) |
actor | A | No | Existing actor instance |
Initialization Options
Section titled “Initialization Options”const reactor = new CandidReactor({ canisterId: "ryjl3-tyaaa-aaaaa-aaaba-cai", clientManager,})
// Fetches Candid from canister metadataawait reactor.initialize()const reactor = new CandidReactor({ canisterId: "ryjl3-tyaaa-aaaaa-aaaba-cai", clientManager, candid: `service : { icrc1_name : () -> (text) query; icrc1_balance_of : (record { owner : principal }) -> (nat) query; }`,})
// Parses provided Candid (no network fetch)await reactor.initialize()const reactor = new CandidReactor({ canisterId: "ryjl3-tyaaa-aaaaa-aaaba-cai", clientManager,})
// Register methods one at a timeawait reactor.registerMethod({ functionName: "icrc1_balance_of", candid: "(record { owner : principal }) -> (nat) query",})Methods
Section titled “Methods”initialize()
Section titled “initialize()”Fetch or parse Candid and update the service definition.
await reactor.initialize()If candid was provided in the constructor, it parses that. Otherwise, fetches from the network.
registerMethod(options)
Section titled “registerMethod(options)”Register a single method by its Candid signature.
await reactor.registerMethod({ functionName: "icrc1_balance_of", candid: "(record { owner : principal }) -> (nat) query",})After registration, all standard Reactor methods work with this function name.
Parameters
Section titled “Parameters”| Option | Type | Description |
|---|---|---|
functionName | string | The method name to register |
candid | string | Candid signature or full service def |
registerMethods(methods)
Section titled “registerMethods(methods)”Register multiple methods at once.
await reactor.registerMethods([ { functionName: "icrc1_name", candid: "() -> (text) query" }, { functionName: "icrc1_symbol", candid: "() -> (text) query" }, { functionName: "icrc1_balance_of", candid: "(record { owner : principal }) -> (nat) query", },])hasMethod(functionName)
Section titled “hasMethod(functionName)”Check if a method is registered.
if (reactor.hasMethod("icrc1_balance_of")) { // Method is available}getMethodNames()
Section titled “getMethodNames()”Get all registered method names.
const methods = reactor.getMethodNames()// ["icrc1_name", "icrc1_symbol", "icrc1_balance_of", ...]One-Shot Dynamic Calls
Section titled “One-Shot Dynamic Calls”For quick calls without explicit registration, use these convenience methods. They register the method (if not already registered) and call it in one step.
queryDynamic(options)
Section titled “queryDynamic(options)”Perform a dynamic query call.
const balance = await reactor.queryDynamic<bigint>({ functionName: "icrc1_balance_of", candid: "(record { owner : principal }) -> (nat) query", args: [{ owner: Principal.fromText("...") }],})callDynamic(options)
Section titled “callDynamic(options)”Perform a dynamic update call.
const result = await reactor.callDynamic({ functionName: "transfer", candid: "(record { to : principal; amount : nat }) -> (variant { Ok : nat; Err : text })", args: [{ to: recipient, amount: 100n }],})fetchQueryDynamic(options)
Section titled “fetchQueryDynamic(options)”Perform a dynamic query with TanStack Query caching.
const cachedBalance = await reactor.fetchQueryDynamic({ functionName: "icrc1_balance_of", candid: "(record { owner : principal }) -> (nat) query", args: [{ owner }],})Standard Reactor Methods
Section titled “Standard Reactor Methods”After initialization or method registration, all inherited Reactor methods work:
| Method | Description |
|---|---|
callMethod() | Direct canister call |
fetchQuery() | Fetch with TanStack Query caching |
getQueryOptions() | Get options for useQuery |
getQueryData() | Get cached data without fetching |
invalidateQueries() | Invalidate cached queries |
generateQueryKey() | Generate cache key for a method |
See Reactor for full documentation.
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
adapter | CandidAdapter | The internal CandidAdapter |
All inherited Reactor properties are also available (canisterId, agent, queryClient, etc.).
Examples
Section titled “Examples”Canister Explorer
Section titled “Canister Explorer”async function exploreCanister(canisterId: string) { const reactor = new CandidReactor({ canisterId, clientManager }) await reactor.initialize()
console.log("Methods:", reactor.getMethodNames())
// Try calling a common method if (reactor.hasMethod("icrc1_name")) { const name = await reactor.callMethod({ functionName: "icrc1_name" }) console.log("Token name:", name) }}Dynamic Token Balance Checker
Section titled “Dynamic Token Balance Checker”async function getTokenBalance(canisterId: string, owner: Principal) { const reactor = new CandidReactor({ canisterId, clientManager })
// Register just the method we need await reactor.registerMethod({ functionName: "icrc1_balance_of", candid: "(record { owner : principal }) -> (nat) query", })
// Use fetchQuery for caching return reactor.fetchQuery({ functionName: "icrc1_balance_of" as any, args: [{ owner }], })}React Hook Integration
Section titled “React Hook Integration”import { useQuery } from "@tanstack/react-query"
function useDynamicQuery( reactor: CandidReactor, functionName: string, candid: string, args?: unknown[]) { return useQuery({ queryKey: [reactor.canisterId.toString(), functionName, args], queryFn: async () => { await reactor.registerMethod({ functionName, candid }) return reactor.callMethod({ functionName: functionName as any, args }) }, })}Best Practices
Section titled “Best Practices”See Also
Section titled “See Also”- CandidAdapter — Low-level Candid utilities
- Reactor — Base Reactor class
- @ic-reactor/candid Overview — Package overview