@ic-reactor/react
v3.6.0
Published
IC Reactor React Library
Maintainers
Readme
@ic-reactor/react
React bindings for IC Reactor. This package re-exports everything from
@ic-reactor/core and adds hook factories, auth hooks, direct reactor hooks,
and reusable query or mutation factories built around TanStack Query.
Install
pnpm add @ic-reactor/react @icp-sdk/core @tanstack/react-query
# Optional: Internet Identity login helpers
pnpm add @icp-sdk/authQuick Start
// src/reactor.ts
import { ClientManager, Reactor, createActorHooks } from "@ic-reactor/react"
import { QueryClient } from "@tanstack/react-query"
import { idlFactory, type _SERVICE } from "./declarations/backend"
export const queryClient = new QueryClient()
export const clientManager = new ClientManager({
queryClient,
withCanisterEnv: true,
})
export const backend = new Reactor<_SERVICE>({
clientManager,
idlFactory,
name: "backend",
})
export const {
useActorQuery,
useActorMutation,
useActorSuspenseQuery,
useActorMethod,
} = createActorHooks(backend)// src/App.tsx
import { QueryClientProvider } from "@tanstack/react-query"
import { queryClient, useActorMethod, useActorQuery } from "./reactor"
function Greeting() {
const { data, isPending } = useActorQuery({
functionName: "greet",
args: ["World"],
})
if (isPending) return <p>Loading...</p>
return <p>{data}</p>
}
function Increment() {
const { call, isPending } = useActorMethod({ functionName: "increment" })
return (
<button disabled={isPending} onClick={() => call([])}>
{isPending ? "Updating..." : "Increment"}
</button>
)
}
export function App() {
return (
<QueryClientProvider client={queryClient}>
<Greeting />
<Increment />
</QueryClientProvider>
)
}Main APIs
createActorHooks(reactor)for per-canister hooks likeuseActorQueryanduseActorMutationcreateAuthHooks(clientManager)foruseAuth,useAgentState,useUserPrincipal, and identity attribute hooks- direct reactor hooks like
useReactorQuerywhen you want to pass the reactor instance at call time - factory helpers like
createQuery,createSuspenseQuery,createInfiniteQuery,createSuspenseInfiniteQuery, andcreateMutationwhen the same operation must work both inside and outside React
Choosing the Right Pattern
- Use
createActorHooksfor the simplest component-first integration. - Use query and mutation factories when you also need loader, action, service,
or test usage through
.fetch(),.prefetch(),.execute(),.invalidate(),.getCacheData(), or.setData(). - Use
DisplayReactorwhen you want UI-friendly values such as strings instead ofbigintorPrincipal. - Use generated hooks from
@ic-reactor/vite-pluginor@ic-reactor/cliwhen you have larger canisters or frequent.didchanges.
Factory Example
import { createSuspenseQueryFactory, createMutation } from "@ic-reactor/react"
import { backend } from "./reactor"
export const getProfile = createSuspenseQueryFactory(backend, {
functionName: "get_profile",
})
export const updateProfile = createMutation(backend, {
functionName: "update_profile",
onCanisterError: (err) => console.error("Canister Err variant:", err.code),
})const profileQuery = getProfile(["alice"])
// React component
const { data } = profileQuery.useSuspenseQuery()
// Prefetch before navigating (fire-and-forget)
profileQuery.prefetch()
// Optimistic update
profileQuery.setData({ id: "alice", name: "Alice" })
// Mutation with cache invalidation
const mutation = updateProfile.useMutation({
invalidateQueries: [profileQuery.getQueryKey()],
})Identity Attributes / OpenID email and profile values
createAuthHooks(clientManager) exposes useIdentityAttributes() for signed
identity attributes from @icp-sdk/auth v7. Existing useAuth(), login(),
logout(), and useUserPrincipal() consumers keep the same IC Reactor API, but
the configured auth client must support the v7 constructor/sign-in/sign-out API.
import { createAuthHooks } from "@ic-reactor/react"
import { clientManager } from "./reactor"
const { useIdentityAttributes } = createAuthHooks(clientManager)
function RegisterWithOpenIdProvider() {
const {
requestOpenIdAttributes,
attributes,
isRequestingAttributes,
attributeError,
} = useIdentityAttributes()
async function handleProviderLogin() {
const nonce = await backend.registerBegin()
const result = await requestOpenIdAttributes({
nonce,
openIdProvider: "microsoft",
keys: ["email", "name"],
windowOpenerFeatures: popupCenter(),
})
console.log(result.decodedAttributes.email)
console.log(result.decodedAttributes.name)
await backend.registerFinish({
data: result.signedAttributes.data,
signature: result.signedAttributes.signature,
})
}
return (
<button disabled={isRequestingAttributes} onClick={handleProviderLogin}>
{attributes?.decodedAttributes.email ??
attributeError?.message ??
"Continue with provider"}
</button>
)
}Use a documented auth provider alias ("google", "apple", or "microsoft")
or the provider issuer URL your app expects for openIdProvider.
Frontend decoded email and name values are for display only. Production flows
must send signedAttributes.data and signedAttributes.signature to the backend
or canister and verify the signature, nonce, origin, timestamp, and requested keys
before trusting or storing the attributes.
Query Result Methods
Every object returned by createQuery, createSuspenseQuery, and their
factory variants exposes:
| Method | Description |
| ----------------------------------- | ----------------------------------------------------------------------------------------------- |
| fetch() | Cache-first fetch — returns data, populates cache. Use in route loaders. |
| prefetch() | Fire-and-forget cache warm-up. Use on hover or before navigation. |
| invalidate() | Invalidates the cache entry (triggers refetch if query is mounted). |
| getQueryKey() | Returns the TanStack Query key for this query. |
| getCacheData(select?) | Read directly from cache without fetching. Returns undefined if not cached. |
| setData(updater) | Write raw data into the cache. Accepts a value or updater function. Use for optimistic updates. |
| useQuery() / useSuspenseQuery() | React hook for the query. |
Canister Error Handling
Canister methods can return Result { Err: E } variants. These are surfaced
as CanisterError and can be handled separately from network or agent errors
via onCanisterError. This callback is supported on both createMutation and
the direct useActorMutation hook:
// Via createActorHooks
const { mutate } = useActorMutation({
functionName: "transfer",
onCanisterError: (err, vars) => {
// err.code — the Err variant key (e.g. "InsufficientFunds")
// err.err — the typed Err value
console.error(`Transfer failed: ${err.code}`, vars)
},
onError: (err) => {
// Fires for ALL errors: canister Err variants, network failures, etc.
console.error("Unexpected error", err)
},
})
// Via createMutation factory
const transferMutation = createMutation(backend, {
functionName: "transfer",
onCanisterError: (err) => toast.error(`${err.code}`),
})Re-exports
@ic-reactor/react re-exports the core runtime, so you can import these from a
single package:
ClientManagerReactorDisplayReactorCallErrorCanisterErrorValidationError
See Also
- Docs: https://ic-reactor.b3pay.net/v3/packages/react
@ic-reactor/core: ../core/README.md@ic-reactor/vite-plugin: ../vite-plugin/README.md@ic-reactor/cli: ../cli/README.md
