React Setup
This guide covers the complete setup of IC Reactor in a React application, including authentication with Internet Identity.
Basic Setup
Section titled “Basic Setup”-
Create the Reactor
Section titled “Create the Reactor”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,}) -
Create Hooks
Section titled “Create Hooks”src/reactor/hooks.ts import { createActorHooks, createAuthHooks } from "@ic-reactor/react"import { backend, clientManager } from "./index"// Actor hooks for your canisterexport 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 authenticationexport 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) -
Setup Provider (Optional)
Section titled “Setup Provider (Optional)”While not required (as hooks are already bound to your reactor and its internal
queryClient), you can wrap your application withQueryClientProviderto 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
Authentication
Section titled “Authentication”useAuth Hook
Section titled “useAuth Hook”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> )}useAgentState Hook
Section titled “useAgentState Hook”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>}useUserPrincipal Hook
Section titled “useUserPrincipal Hook”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>}Complete Auth Component
Section titled “Complete Auth Component”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> )}Login Options
Section titled “Login Options”Customize the login flow:
const { login } = useAuth()
// With optionslogin({ 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) },})Protected Routes
Section titled “Protected Routes”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> }/>Multiple Actors
Section titled “Multiple Actors”Working with multiple canisters:
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 canisterexport const ledger = new Reactor<Ledger>({ clientManager, name: "ledger", idlFactory: ledgerIdl, canisterId: import.meta.env.VITE_LEDGER_CANISTER_ID,})
// Backend canisterexport const backend = new Reactor<Backend>({ clientManager, name: "backend", idlFactory: backendIdl, canisterId: import.meta.env.VITE_BACKEND_CANISTER_ID,})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 convenienceexport 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)} = authHooksAuto Transformations
Section titled “Auto Transformations”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.Session Persistence
Section titled “Session Persistence”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):
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 />}Best Practices
Section titled “Best Practices”- Centralize configuration — Keep reactor setup in a dedicated
reactor/folder - Use environment variables — Store canister IDs in
.envfiles - Enable DevTools — Use React Query DevTools in development
- Handle auth state — Always check
isAuthenticatingbefore showing login UI - Name your actors — Use the
nameoption for better debugging - Use Suspense boundaries — Wrap components using
useActorSuspenseQueryin<Suspense>