@atzentis/edu-react
v0.2.0
Published
Atzentis Edu React — hooks and components for edu.atzentis.io
Maintainers
Readme
@atzentis/edu-react
React hooks and components for the Atzentis Edu SDK.
Installation
npm install @atzentis/edu-react @atzentis/edu-sdk react react-domProviders
EduProvider
Wraps your application and makes the EduClient available to all descendants.
Supply either a pre-constructed client instance, or pass apiKey + tenantId
for inline construction.
// app/providers.tsx (Next.js 16 App Router)
"use client";
import { EduProvider } from "@atzentis/edu-react";
import type { ReactNode } from "react";
export function Providers({ children }: { children: ReactNode }) {
return (
<EduProvider
apiKey={process.env.NEXT_PUBLIC_EDU_KEY!}
tenantId="acme"
>
{children}
</EduProvider>
);
}// app/layout.tsx (Server Component — no "use client" needed here)
import { Providers } from "./providers";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}The EduClient is created once via lazy useState initialisation — its
reference is stable across re-renders, React Strict Mode double-invocations,
and Fast Refresh.
SSR / Next.js App Router
EduProvider carries "use client" and is safe to import in any Server
Component tree. No window or document access occurs at module scope —
all browser-specific work happens inside React lifecycle hooks, so there are
no hydration mismatches.
For streaming-compatible layouts, place <Providers> inside your root layout's
<body> and let Next.js stream the shell. The provider renders synchronously and
does not block the initial render.
SessionProvider
Provides the current authenticated session to descendants.
"use client";
import { EduProvider, SessionProvider } from "@atzentis/edu-react";
export function Providers({ children, session }) {
return (
<EduProvider apiKey={process.env.NEXT_PUBLIC_EDU_KEY!} tenantId="acme">
<SessionProvider session={session}>
{children}
</SessionProvider>
</EduProvider>
);
}// app/page.tsx — Server Component
import { getSession } from "@/lib/auth";
import { Providers } from "./providers";
export default async function Page({ children }) {
const session = await getSession();
return <Providers session={session}>{children}</Providers>;
}Hooks
useEdu
Returns the EduClient from the nearest <EduProvider>. Throws if called
outside a provider.
"use client";
import { useEdu } from "@atzentis/edu-react";
export function SpaceList() {
const edu = useEdu();
async function handleCreate() {
const space = await edu.spaces.createSpace({ name: "Algebra 101" });
console.info(space.id);
}
return <button type="button" onClick={handleCreate}>New Space</button>;
}All service accessors (tools, tutor, spaces, missionControl,
smartModules, annotations, examiner, exams, grading,
accessibility, analytics) are available directly on the returned client.
useSession
Returns the current Session from the nearest <SessionProvider>, or null
if no provider is present.
"use client";
import { useSession } from "@atzentis/edu-react";
export function UserGreeting() {
const session = useSession();
if (!session) return null;
return <span>Signed in as {session.userId} · {session.locale}</span>;
}EduErrorBoundary
Catches descendant render errors and displays a fallback. AbortError (from
cancelled requests) is silently filtered and never shows the fallback.
import { EduErrorBoundary } from "@atzentis/edu-react";
export function LessonShell({ children }) {
return (
<EduErrorBoundary
fallback={({ error, resetErrorBoundary }) => (
<div role="alert">
<p>Something went wrong: {error.message}</p>
<button type="button" onClick={resetErrorBoundary}>Try again</button>
</div>
)}
onError={(err, info) => console.error(err, info)}
>
{children}
</EduErrorBoundary>
);
}When no fallback prop is provided, a default accessible fallback with
role="alert" and a focusable reset button is rendered automatically.
Service Hooks
All service hooks require both <EduProvider> and a TanStack Query
<QueryClientProvider> in the tree:
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { EduProvider } from "@atzentis/edu-react";
const queryClient = new QueryClient();
<QueryClientProvider client={queryClient}>
<EduProvider apiKey={key} tenantId="acme">
{children}
</EduProvider>
</QueryClientProvider>;Each hook returns query/mutation objects from TanStack Query. Query keys are
namespaced under ['edu', '<service>', ...] so you can invalidate precisely.
useTutor()
const tutor = useTutor();
const sessions = tutor.sessions(); // useQuery
await tutor.createSession.mutateAsync({ studentId });Streaming chat uses the dedicated useTutorStream():
const { events, isStreaming, error } = useTutorStream(sessionId, messageId);useSpaces() / useSidekickStream()
const spaces = useSpaces();
const list = spaces.spaces();
const templates = spaces.templates();
await spaces.createSpace.mutateAsync({ name: "Algebra 101" });
const { events } = useSidekickStream(spaceId, { context: { topic: "fractions" } });useTools() / useToolRun()
const tools = useTools();
const all = tools.list();
const categories = tools.categories();
const { run, isPolling } = useToolRun(runId); // polls until terminaluseExaminer()
const examiner = useExaminer();
const sessions = examiner.sessions();
const stages = examiner.stages(sessionId);
await examiner.advanceStage.mutateAsync(sessionId);
const { job } = useScoringJob(jobId); // polls scoring
const { job: report } = useReportJob(reportJobId);useMissionControl() / useMissionControlStream()
const mc = useMissionControl();
const dashboard = mc.dashboard();
const insights = mc.studentInsights(studentId);
await mc.actOnAlert.mutateAsync({ interventionId, action });
const { events } = useMissionControlStream({ classId });useGrading()
const grading = useGrading();
await grading.gradeQuiz.mutateAsync({ quizId, attempt });
const { job } = useEssayGradeJob(jobId); // polls essay grading
const speaking = useSpeakingGrade(sessionId);useAnalytics()
const analytics = useAnalytics();
const usage = analytics.usage({ from, to });
const engagement = analytics.engagement({ from, to });
const roi = analytics.roi({ cohortId });Components
Pre-built, accessible UI components that compose the hooks above with
@atzentis/ui-shadcn primitives. Each requires <EduProvider> and a
<QueryClientProvider> ancestor.
<TutorChat>
import { TutorChat } from "@atzentis/edu-react";
<TutorChat sessionId="session-1" />;Scrolling message list + composer; streams the assistant reply via
useTutorStream. Enter sends, Shift+Enter inserts a newline.
<SpaceViewer>
<SpaceViewer spaceId="space-1" />Tabbed view: content, Sidekick suggestions, member permissions, and a template picker.
<ToolsPanel>
<ToolsPanel />Category list → tool list → parameter form → results pane. Executes tools
and polls long-running runs via useToolRun.
<ExaminerUI>
<ExaminerUI examSessionId="exam-1" scoringJobId={jobId} />Speaking-exam surface: stage progression, browser audio recording
(MediaRecorder), and AI scoring display polled via useScoringJob.
<MissionControlDashboard>
<MissionControlDashboard />Live session list, alert sidebar with dismiss/act actions, filter bar, and a student drill-down panel.
