Skip to content
IC Reactor

createReactorProvider

createReactorProvider<TValue, TProps>(factory, options?): CreateReactorProviderReturn<TValue, NonNullable<TProps>>

Defined in: react/src/createReactorProvider.ts:196

Creates a provider that builds an app’s reactors once per mounted tree, and a useReactor() hook that returns what it built, fully typed.

This is the setup a server-rendered app needs. The factory runs in a useState initializer, once per mounted tree, and a server render is a tree of its own: each request builds its own ClientManager, QueryClient, reactors and AuthenticationManager, so no cache or identity is shared between visitors. The first render needs no effect, so the server’s HTML is complete, and the browser builds a fresh value from the same props to hydrate it. In a client-only app it works the same, one value per mount.

The factory returns anything: a defineReactor or defineDisplayReactor result, a record of them, reactors and managers built by hand, query and mutation objects from createQuery or createMutation. useReactor() returns it with the factory’s own return type, so its hooks keep their generic signatures. useReactor("todo") returns one property of it.

When the tree unmounts, the provider disposes each AuthenticationManager built for the value, releasing the Internet Identity client it built: those constructed while the factory ran, and the one a defineReactor result in the value, or among its own properties, builds on first use (a result that never used authentication builds none). A manager built elsewhere and handed in, such as an app-wide one passed as authentication or as a prop, is left to whoever built it. dispose() only forgets the client, and the next sign-in builds a new one, so this is safe under StrictMode, which runs the cleanup and then the effect again on the same value.

It also renders a QueryClientProvider for the value’s QueryClient; see CreateReactorProviderOptions.queryClientProvider.

A suspense hook below the provider may suspend its first render: React renders the same element again when the data arrives, and the provider reuses the value that first render built, whether the Suspense boundary sits above the provider or, while hydrating, there is none. A provider that a transition mounts (startTransition, a client-side navigation) can be rendered from a new element on each retry and build a new value each time, so give its suspending components a <Suspense> boundary inside it.

Call createReactorProvider at module scope: it builds nothing itself, only the context, and every mounted provider builds its own value. In the Next.js App Router that module needs "use client", like any module with hooks; a server component renders the provider from there.

TValue extends object

TProps extends object | undefined = { }

(props) => TValue

Builds the value. It receives the provider’s props other than children, and runs once per mounted provider.

CreateReactorProviderOptions = {}

See CreateReactorProviderOptions.

CreateReactorProviderReturn<TValue, NonNullable<TProps>>

One canister, with Internet Identity

"use client"
import { createReactorProvider, defineReactor } from "@ic-reactor/react"
import { canisterId, idlFactory, type _SERVICE } from "./declarations/todo"
export const { ReactorProvider, useReactor } = createReactorProvider(() =>
defineReactor<_SERVICE>({ name: "todo", idlFactory, canisterId })
)
function Todos() {
const { useActorQuery, useAuth } = useReactor()
const { isAuthenticated } = useAuth()
const { data } = useActorQuery({ functionName: "getAllTodos" })
return <p>{isAuthenticated ? data?.length : "Sign in"}</p>
}
// app/layout.tsx (a server component)
// <ReactorProvider>{children}</ReactorProvider>

Several canisters sharing one agent and one sign-in

export const { ReactorProvider, useReactor } = createReactorProvider(() => {
const backend = defineReactor<Backend>({
name: "backend",
idlFactory: backendIdl,
canisterId: backendId,
})
const ledger = defineDisplayReactor<Ledger>({
name: "ledger",
idlFactory: ledgerIdl,
canisterId: ledgerId,
authentication: backend.authentication,
})
return { backend, ledger }
})
function Balance() {
const { data } = useReactor("ledger").useActorQuery({
functionName: "icrc1_total_supply",
})
return <span>{data}</span>
}

Props for the factory, and a new key to build again

export const { ReactorProvider: LedgerProvider, useReactor: useLedger } =
createReactorProvider(({ canisterId }: { canisterId: string }) =>
defineDisplayReactor<Ledger>({ name: "ledger", idlFactory, canisterId })
)
// Read once per mount: the key makes a new canister a new tree.
<LedgerProvider key={canisterId} canisterId={canisterId}>
<TokenPage />
</LedgerProvider>