IC Reactor is a monorepo of libraries for building Internet Computer (ICP) apps with:
- end-to-end TypeScript types
- TanStack Query-powered caching and refetching
- React hook factories (
useActorQuery,useActorMutation, etc.) - display-friendly transforms (
DisplayReactor) - optional code generation (CLI + Vite plugin)
IC Reactor gives you a higher-level API than raw Actor usage while keeping type safety and control:
- typed canister method calls
- built-in cache keys and invalidation primitives
- typed
Ok/Errresult handling - shared auth/agent management via
ClientManager - reusable query/mutation objects that work both inside and outside React
| Package | Purpose |
|---|---|
@ic-reactor/core |
Core runtime (ClientManager, Reactor, DisplayReactor, cache integration) |
@ic-reactor/react |
React hooks + query/mutation factories |
@ic-reactor/candid |
Dynamic Candid parsing and runtime reactors |
@ic-reactor/parser |
Local Candid parser (WASM-based) |
@ic-reactor/codegen |
Shared codegen pipeline used by CLI and Vite plugin |
@ic-reactor/cli |
Generate declarations + typed hooks/reactors |
@ic-reactor/vite-plugin |
Vite plugin for watch-mode hook generation |
pnpm add @ic-reactor/react @icp-sdk/core @tanstack/react-querypnpm add @ic-reactor/core @icp-sdk/core @tanstack/query-core# Internet Identity auth helpers
pnpm add @icp-sdk/auth
# Dynamic Candid support (explorers/dev tools)
pnpm add @ic-reactor/candid @ic-reactor/parser// src/reactor.ts
import { ClientManager, Reactor } from "@ic-reactor/react"
import { QueryClient } from "@tanstack/react-query"
import { idlFactory, type _SERVICE } from "./declarations/my_canister"
export const queryClient = new QueryClient()
export const clientManager = new ClientManager({
queryClient,
// withCanisterEnv: true, // optional: useful in local/dev setups
})
export const backendReactor = new Reactor<_SERVICE>({
clientManager,
idlFactory,
name: "backend",
canisterId: "rrkah-fqaaa-aaaaa-aaaaq-cai",
})// src/hooks.ts
import { createActorHooks, createAuthHooks } from "@ic-reactor/react"
import { backendReactor, clientManager } from "./reactor"
export const {
useActorQuery,
useActorMutation,
useActorSuspenseQuery,
useActorInfiniteQuery,
} = createActorHooks(backendReactor)
export const { useAuth, useUserPrincipal } = createAuthHooks(clientManager)// src/App.tsx
import { QueryClientProvider } from "@tanstack/react-query"
import { queryClient } from "./reactor"
import { useActorQuery, useActorMutation, useAuth } from "./hooks"
function Greeting() {
const { data, isPending, error } = useActorQuery({
functionName: "greet",
args: ["World"],
})
if (isPending) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return <h1>{data}</h1>
}
function AuthButton() {
const { login, logout, isAuthenticated, principal } = useAuth()
return isAuthenticated ? (
<button onClick={() => logout()}>
Logout {principal?.toText().slice(0, 8)}...
</button>
) : (
<button onClick={() => login()}>Login</button>
)
}
function UpdateProfileButton() {
const { mutate, isPending } = useActorMutation({
functionName: "update_profile",
})
return (
<button
disabled={isPending}
onClick={() => mutate([{ name: "Alice", bio: "Hello IC" }])}
>
{isPending ? "Saving..." : "Save"}
</button>
)
}
export function App() {
return (
<QueryClientProvider client={queryClient}>
<AuthButton />
<Greeting />
<UpdateProfileButton />
</QueryClientProvider>
)
}Use when component code can pass functionName and args inline.
- Best for straightforward React integration
- Single typed hook suite per reactor
Use when the same operation must be used:
- inside React components
- in route loaders/actions
- in services or test helpers
import { createQuery, createMutation } from "@ic-reactor/react"
import { backendReactor } from "./reactor"
export const getProfile = createQuery(backendReactor, {
functionName: "get_profile",
})
export const updateProfile = createMutation(backendReactor, {
functionName: "update_profile",
invalidateQueries: [getProfile.getQueryKey()],
})Inside React:
const { data } = getProfile.useQuery()
const { mutateAsync } = updateProfile.useMutation({
onSettled: () => toast.success("Profile updated!"),
})Outside React:
await getProfile.fetch()
const cached = getProfile.getCacheData()
await updateProfile.execute([{ name: "Alice" }])Important: Do not call React hooks (useActorQuery, .useQuery(), .useMutation()) outside React components or custom hooks.
Use DisplayReactor when you want transformed values for UI/forms (for example, bigint and Principal represented as strings).
import { DisplayReactor } from "@ic-reactor/react"For larger canisters or frequent .did changes, prefer generated hooks.
// vite.config.ts
import { defineConfig } from "vite"
import react from "@vitejs/plugin-react"
import { icReactor } from "@ic-reactor/vite-plugin"
export default defineConfig({
plugins: [
react(),
icReactor({
canisters: [{ name: "backend", didFile: "./backend/backend.did" }],
}),
],
})npx @ic-reactor/cli init
npx @ic-reactor/cli generateGenerated query/mutation files typically expose both:
- React methods (
.useQuery(),.useMutation()) - imperative methods (
.fetch(),.execute(),.invalidate())
import { CandidDisplayReactor } from "@ic-reactor/candid"
import { ClientManager } from "@ic-reactor/core"
const clientManager = new ClientManager()
const reactor = new CandidDisplayReactor({
canisterId: "ryjl3-tyaaa-aaaaa-aaaba-cai",
clientManager,
})
await reactor.initialize()
const balance = await reactor.callMethod({
functionName: "icrc1_balance_of",
args: [{ owner: "aaaaa-aa" }],
})
console.log(balance)| Feature | Standard Actor | IC Reactor |
|---|---|---|
| Type-safe method calls | ✅ | ✅ |
| Query caching | ❌ | ✅ |
| Background refetching | ❌ | ✅ |
Typed Ok/Err handling |
❌ (manual) | ✅ |
| Shared auth/identity + cache coordination | ❌ | ✅ (ClientManager) |
| Display-friendly transforms | ❌ | ✅ (DisplayReactor) |
| Example | Description |
|---|---|
all-in-one-demo |
End-to-end demo with queries, mutations, suspense, infinite queries |
tanstack-router |
Router loaders/actions + generated hooks |
query-demo |
Query and mutation factory patterns |
multiple-canister |
Shared auth across multiple canisters |
ckbtc-wallet |
More advanced canister integrations |
codegen-in-action |
CLI vs Vite plugin codegen comparison |
typescript-demo |
Core usage without React |
candid-parser |
Dynamic Candid parsing |
- Docs site source:
./docs - Package docs:
Run docs locally:
cd docs
pnpm install
pnpm dev# Install dependencies
pnpm install
# Build packages
pnpm build
# Run package tests
pnpm test
# Run e2e tests
pnpm test-e2e
# Build docs
pnpm docs:buildThis repository is intentionally structured to work well with AI coding assistants and agents.
./llms.txt— high-level library context for LLMsB3Pay/ic-reactor-skills— installable IC Reactor skills mono-repo./.cursorrules— Cursor-specific behavior guidance
The canonical installable IC Reactor hooks skill lives in the skills mono-repo:
- repo:
B3Pay/ic-reactor-skills - skill path:
ic-reactor-hooks
Use it when asking an agent to:
- create/refactor
createActorHooks(...)integrations - build reusable
createQuery/createMutationmodules - explain inside-React vs outside-React usage (
fetch,execute,invalidate) - choose between manual hooks and generated hooks (CLI / Vite plugin)
Example prompt:
Use $ic-reactor-hooks to create a reusable query/mutation factory pair for my canister and show usage both inside a React component and in a route loader.
Example install:
npx skills add B3Pay/ic-reactor-skills --full-depth --skill ic-reactor-hooksSee CONTRIBUTING.md for development workflow, formatting, release notes, and AI-assisted contribution guidance.
Please also review the Code of Conduct.
MIT © Behrad Deylami