Custom Provider
This example demonstrates how to build a Token Viewer where the user can input any Canister ID, and the app dynamically connects to it. A provider built with createReactorProvider instantiates a Reactor from its props and hands its hooks to the components below it.
Features Demonstrated
Section titled “Features Demonstrated”- Dynamic Canister IDs: Connecting to canisters determined at runtime.
- Props for the factory:
createReactorProviderpasses the provider’s props to the factory that builds the reactor. - Reusability: A generic
ICRC1Providerthat can wrap any part of the app, typed throughuseICRC1Context()without a hand-written context.
Open in StackBlitzEdit and run in your browser
View on GitHubBrowse the source code
Live Preview
Section titled “Live Preview”Implementation Details
Section titled “Implementation Details”The Dynamic Provider
Section titled “The Dynamic Provider”import { Reactor, createActorHooks, createReactorProvider,} from "@ic-reactor/react"import { idlFactory, type ICRC1 } from "./declarations/icrc1"import { clientManager } from "./reactor"
const { ReactorProvider, useReactor } = createReactorProvider( ({ canisterId }: { canisterId: string }) => { const reactor = new Reactor<ICRC1>({ name: "icrc1", clientManager, canisterId, idlFactory, }) return { canisterId, hooks: createActorHooks(reactor) } })
export const useICRC1Context = useReactor
export default ReactorProviderThe factory runs once, when the provider mounts. App gives the provider a
key, so a new canister ID is a new provider and the factory builds a reactor
for it:
// src/App.tsx (simplified: the demo's App also renders the token cards and login)import { useState, type FormEvent } from "react"import { isPrincipalText } from "@ic-reactor/react"import ICRC1Call from "./ICRC1Call"import ICRC1Provider from "./ICRC1Provider"
export default function App() { const [canisterId, setCanisterId] = useState("ryjl3-tyaaa-aaaaa-aaaba-cai") const [draft, setDraft] = useState(canisterId)
// Only text that is a principal becomes the canister ID, so a half-typed // value never reaches the reactor factory. const onSubmit = (event: FormEvent) => { event.preventDefault() const text = draft.trim() if (isPrincipalText(text)) { setCanisterId(text) } else { console.error(`Not a canister ID: ${text}`) } }
return ( <> <form onSubmit={onSubmit}> <input value={draft} onChange={(event) => setDraft(event.target.value)} /> <button type="submit">Fetch Token</button> </form> <ICRC1Provider key={canisterId} canisterId={canisterId}> <ICRC1Call functionName="icrc1_symbol" label="Symbol" /> </ICRC1Provider> </> )}Components read the hooks with useICRC1Context(), typed as the factory’s
return value, so hooks.useActorQuery checks functionName against the ICRC1
service. Every reactor shares the app’s ClientManager, so one sign-in covers
every token, and each canister’s queries are cached under its own ID.