Skip to content
IC Reactor

Next.js Integration

This example shows how to use IC Reactor in an existing Next.js app that still uses dfx for local deployment and declaration generation.

  • Next.js Pages Router: The app lives under src/pages, not the App Router.
  • Rust Backend: A local canister under backend/.
  • Manual declaration flow: dfx generate output is committed under src/declarations/todo/.
  • IC Reactor hooks: createActorHooks wraps a Reactor built from those generated declarations.
Terminal window
cd examples/nextjs
npm run install:all
npm run dfx:start
npm run deploy
npm run generate
npm run dev
src/service/provider.tsx
import { createContext, useContext, useState } from "react"
import type { ReactNode } from "react"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
import {
ClientManager,
AuthenticationManager,
Reactor,
createActorHooks,
createAuthHooks,
} from "@ic-reactor/react"
import { canisterId, idlFactory } from "declarations/todo"
import type { _SERVICE } from "declarations/todo/todo.did"
function createReactorContext() {
const queryClient = new QueryClient()
const clientManager = new ClientManager({
queryClient,
agentOptions: {
host: process.env.NEXT_PUBLIC_IC_HOST || "http://127.0.0.1:4943",
},
})
const authentication = new AuthenticationManager({ clientManager })
const todoReactor = new Reactor<_SERVICE>({
name: "todo",
clientManager,
canisterId,
idlFactory,
})
return {
queryClient,
clientManager,
authentication,
todoReactor,
auth: createAuthHooks(authentication),
todo: createActorHooks(todoReactor),
}
}
type ReactorContextValue = ReturnType<typeof createReactorContext>
const ReactorContext = createContext<ReactorContextValue | null>(null)
export function ICReactorProvider({ children }: { children: ReactNode }) {
// Runs once per mounted tree — and a server render is its own tree, so each
// request gets its own managers and its own cache.
const [value] = useState(createReactorContext)
return (
<QueryClientProvider client={value.queryClient}>
<ReactorContext.Provider value={value}>
{children}
</ReactorContext.Provider>
</QueryClientProvider>
)
}
export function useICReactor() {
const context = useContext(ReactorContext)
if (!context) {
throw new Error("useICReactor must be used inside <ICReactorProvider>")
}
return context
}

Bind the hooks with the hook’s own function type rather than a Parameters<...> wrapper — useActorQuery is generic over the method name, and a rest-args wrapper collapses it to the base signature, degrading data to unknown at every call site:

type TodoHooks = ReturnType<typeof createReactorContext>["todo"]
export const useQueryTodo: TodoHooks["useActorQuery"] = (...args: any[]) =>
(useICReactor().todo.useActorQuery as any)(...args)

Then wrap the app once, and keep anything that touches the reactor below it:

src/pages/_app.tsx
const App: React.FC<AppProps> = (props) => (
<ICReactorProvider>
<AppShell {...props} />
</ICReactorProvider>
)