Result Types Demo
This example renders canister return values — records, variants, vectors,
tuples, recursive types and func references — from the metadata
ResultFieldVisitor builds for a service’s return types. Its ResultRenderer
also has components for blobs and optionals, which the sample values do not
reach. It works on a local IDL and sample values, so it needs no canister.
Features Demonstrated
Section titled “Features Demonstrated”- Result Metadata:
ResultFieldVisitor.visitServicebuilds oneMethodMetaper method. - Resolution:
resolve()turns a raw Candid value into display-ready nodes. - Recursive Types: a
Categorythat contains itself renders level by level. - Func References: an ICRC ledger’s archived-block callbacks render as canister and method.
Open in StackBlitzEdit and run in your browser
View on GitHubBrowse the source code
Live Preview
Section titled “Live Preview”Implementation
Section titled “Implementation”// An excerpt of src/App.tsx: the demo also has a `get_version` method and shows// the resolved structure as JSONimport { ResultFieldVisitor } from "@ic-reactor/candid"import { IDL } from "@icp-sdk/core/candid"import { Principal } from "@icp-sdk/core/principal"import { ResultRenderer } from "./components/ResultRenderer"
const testIDL: IDL.InterfaceFactory = ({ IDL }) => { const User = IDL.Record({ name: IDL.Text, age: IDL.Nat, is_active: IDL.Bool, tags: IDL.Vec(IDL.Text), role: IDL.Variant({ Admin: IDL.Null, User: IDL.Null, Guest: IDL.Null }), identity: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Principal)), })
// A type that contains itself const Category = IDL.Rec() Category.fill( IDL.Record({ name: IDL.Text, subcategories: IDL.Vec(Category) }) )
// Real-world pattern: ICRC ledger archived blocks, a record with a func const GetBlocksArgs = IDL.Record({ start: IDL.Nat64, length: IDL.Nat64 }) const BlockRange = IDL.Record({ blocks: IDL.Vec(IDL.Nat8) }) const ArchivedBlocksRange = IDL.Record({ callback: IDL.Func([GetBlocksArgs], [BlockRange], ["query"]), start: IDL.Nat64, length: IDL.Nat64, })
return IDL.Service({ get_user: IDL.Func([], [User], ["query"]), get_categories: IDL.Func([], [Category], ["query"]), get_archived_blocks: IDL.Func( [], [IDL.Vec(ArchivedBlocksRange)], ["query"] ), })}
function App() { const visitor = new ResultFieldVisitor() const serviceMeta = visitor.visitService(testIDL({ IDL }))
// Raw Candid values that match each method's return type const mockUser = { name: "Alice", age: 30n, is_active: true, tags: ["react", "candid", "icp"], role: { Admin: null }, identity: [["Alice", Principal.fromText("aaaaa-aa")]], } const mockCategories = { name: "Electronics", subcategories: [ { name: "Computers", subcategories: [{ name: "Laptops", subcategories: [] }], }, { name: "Phones", subcategories: [] }, ], } const archive = Principal.fromText("qjdve-lqaaa-aaaaa-aaaeq-cai") const mockArchivedBlocks = [ { callback: [archive, "get_blocks"], start: 1000n, length: 500n }, ]
// Each method's metadata resolves a raw Candid value into display-ready nodes const resolved = serviceMeta["get_user"].resolve(mockUser) const resolvedCategories = serviceMeta["get_categories"].resolve(mockCategories) const resolvedArchived = serviceMeta["get_archived_blocks"].resolve(mockArchivedBlocks)
// One node per return value, each carrying its display value return ( <> <h2>Dynamic Record Rendering</h2> {resolved.results.map((result, i) => ( <ResultRenderer key={i} result={result} /> ))}
<h2>Recursive Type Rendering</h2> {resolvedCategories.results.map((result, i) => ( <ResultRenderer key={i} result={result} /> ))}
<h2>FuncRecord Rendering (Archived Blocks)</h2> {resolvedArchived.results.map((result, i) => ( <ResultRenderer key={i} result={result} /> ))} </> )}