Skip to content

React Setup

This guide covers the complete setup of IC Reactor in a React application, including authentication with Internet Identity.

  1. Set up your central configuration:

    src/reactor/index.ts
    import { ClientManager, Reactor } from "@ic-reactor/core"
    import { QueryClient } from "@tanstack/query-core"
    import { idlFactory, type _SERVICE } from "../declarations/backend"
    export const queryClient = new QueryClient()
    export const clientManager = new ClientManager({
    queryClient,
    withProcessEnv: true,
    })
    export const backend = new Reactor<_SERVICE>({
    clientManager,
    idlFactory,
    canisterId: import.meta.env.VITE_BACKEND_CANISTER_ID,
    })
  2. src/reactor/hooks.ts
    import { createActorHooks, createAuthHooks } from "@ic-reactor/react"
    import { backend, clientManager } from "./index"
    // Actor hooks for your canister
    export const {
    useActorQuery, // [Read more](/ic-reactor/v3/reference/createactorhooks/useactorquery)
    useActorMutation, // [Read more](/ic-reactor/v3/reference/createactorhooks/useactormutation)
    useActorSuspenseQuery, // [Read more](/ic-reactor/v3/reference/createactorhooks/useactorsuspensequery)
    useActorInfiniteQuery, // [Read more](/ic-reactor/v3/reference/createactorhooks/useactorinfinitequery)
    } = createActorHooks(backend)
    // Auth hooks for authentication
    export const {
    useAuth, // [Read more](/ic-reactor/v3/reference/createauthhooks/useauth)
    useUserPrincipal, // [Read more](/ic-reactor/v3/reference/createauthhooks/useuserprincipal)
    useAgentState, // [Read more](/ic-reactor/v3/reference/createauthhooks/useagentstate)
    } = createAuthHooks(clientManager)
  3. While not required (as hooks are already bound to your reactor and its internal queryClient), you can wrap your application with QueryClientProvider to use additional TanStack Query features or to enable the DevTools:

    src/App.tsx
    import { QueryClientProvider } from "@tanstack/react-query"
    import { ReactQueryDevtools } from "@tanstack/react-query-devtools"
    import { queryClient } from "./reactor"
    import Router from "./Router"
    function App() {
    return (
    <QueryClientProvider client={queryClient}>
    <Router />
    <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
    )
    }
    export default App

The useAuth hook provides login and logout functionality:

import { useAuth } from "../reactor/hooks"
function LoginButton() {
const { login, logout, isAuthenticated, isAuthenticating } = useAuth()
if (isAuthenticating) {
return <button disabled>Connecting...</button>
}
return isAuthenticated ? (
<button onClick={logout}>Logout</button>
) : (
<button onClick={() => login()}>Login with Internet Identity</button>
)
}

For accessing the useAgentState connection state:

import { useAgentState } from "../reactor/hooks"
function ConnectionStatus() {
const {
isInitialized, // boolean
isInitializing, // boolean
network, // 'ic' | 'local' | 'remote'
} = useAgentState()
if (isInitializing) {
return <span>Connecting to network...</span>
}
return <span>Connected to {network}</span>
}

A convenience hook for getting the current user’s useUserPrincipal:

import { useUserPrincipal } from "../reactor/hooks"
function UserBadge() {
const principal = useUserPrincipal()
if (!principal) return null
return <div className="badge">{principal.toText().slice(0, 8)}...</div>
}
import { useAuth, useUserPrincipal } from "../reactor/hooks"
function AuthSection() {
const { login, logout, isAuthenticated, isAuthenticating } = useAuth()
const principal = useUserPrincipal()
if (isAuthenticating) {
return <div className="auth">Connecting to Internet Identity...</div>
}
return (
<div className="auth">
{isAuthenticated ? (
<>
<span>Welcome, {principal?.toText().slice(0, 12)}...</span>
<button onClick={logout}>Logout</button>
</>
) : (
<button onClick={() => login()}>Login with Internet Identity</button>
)}
</div>
)
}

Customize the login flow:

const { login } = useAuth()
// With options
login({
identityProvider: "https://identity.ic0.app",
maxTimeToLive: BigInt(7 * 24 * 60 * 60 * 1_000_000_000), // 7 days
onSuccess: () => {
console.log("Logged in successfully!")
navigate("/dashboard")
},
onError: (error) => {
console.error("Login failed:", error)
},
})

Create a wrapper for protected routes:

import { useAuth } from "../reactor/hooks"
import { Navigate, useLocation } from "react-router-dom"
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { isAuthenticated, isAuthenticating } = useAuth()
const location = useLocation()
if (isAuthenticating) {
return <LoadingSpinner />
}
if (!isAuthenticated) {
return <Navigate to="/login" state={{ from: location }} replace />
}
return children
}
// Usage in router
;<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>

Working with multiple canisters:

src/reactor/index.ts
import { ClientManager, Reactor } from "@ic-reactor/core"
import { QueryClient } from "@tanstack/query-core"
import {
idlFactory as ledgerIdl,
type _SERVICE as Ledger,
} from "../declarations/ledger"
import {
idlFactory as backendIdl,
type _SERVICE as Backend,
} from "../declarations/backend"
export const queryClient = new QueryClient()
export const clientManager = new ClientManager({ queryClient })
// Ledger canister
export const ledger = new Reactor<Ledger>({
clientManager,
name: "ledger",
idlFactory: ledgerIdl,
canisterId: import.meta.env.VITE_LEDGER_CANISTER_ID,
})
// Backend canister
export const backend = new Reactor<Backend>({
clientManager,
name: "backend",
idlFactory: backendIdl,
canisterId: import.meta.env.VITE_BACKEND_CANISTER_ID,
})
src/reactor/hooks.ts
import { createActorHooks, createAuthHooks } from "@ic-reactor/react"
import { ledger, backend, clientManager } from "./index"
export const ledgerHooks = createActorHooks(ledger)
export const backendHooks = createActorHooks(backend)
export const authHooks = createAuthHooks(clientManager)
// Export individual hooks for convenience
export const {
useActorQuery: useLedgerQuery, // [Read more](/ic-reactor/v3/reference/createactorhooks/useactorquery)
useActorMutation: useLedgerMutation, // [Read more](/ic-reactor/v3/reference/createactorhooks/useactormutation)
} = ledgerHooks
export const {
useActorQuery: useBackendQuery, // [Read more](/ic-reactor/v3/reference/createactorhooks/useactorquery)
useActorMutation: useBackendMutation, // [Read more](/ic-reactor/v3/reference/createactorhooks/useactormutation)
} = backendHooks
export const {
useAuth, // [Read more](/ic-reactor/v3/reference/createauthhooks/useauth)
useUserPrincipal, // [Read more](/ic-reactor/v3/reference/createauthhooks/useuserprincipal)
} = authHooks

For easier handling of Candid types, use DisplayReactor:

import { ClientManager, DisplayReactor } from "@ic-reactor/core"
import { idlFactory, type _SERVICE } from "../declarations/backend"
const backend = new DisplayReactor<_SERVICE>({
clientManager,
idlFactory,
canisterId: "...",
})
// Now BigInt values are returned as strings,
// Principals as text, etc.

Sessions are automatically persisted in IndexedDB. Authentication hooks like useAuth automatically initialize the session restoration on first use.

To manually initialize explicitly on app load (optional):

src/App.tsx
import { useEffect } from "react"
import { clientManager } from "./reactor"
function App() {
useEffect(() => {
clientManager.initialize()
}, [])
return <YourApp />
}

Or check agent state:

import { useAgentState } from "./reactor/hooks"
function App() {
const { isInitialized, isInitializing, error } = useAgentState()
if (isInitializing) {
return <LoadingScreen />
}
if (error) {
return <ErrorScreen error={error} />
}
return <YourApp />
}
  1. Centralize configuration — Keep reactor setup in a dedicated reactor/ folder
  2. Use environment variables — Store canister IDs in .env files
  3. Enable DevTools — Use React Query DevTools in development
  4. Handle auth state — Always check isAuthenticating before showing login UI
  5. Name your actors — Use the name option for better debugging
  6. Use Suspense boundaries — Wrap components using useActorSuspenseQuery in <Suspense>