The @edpire/sdk/react entry exports three React components:
| Component | Use when |
|---|
EdpireAssessmentPlayer | You want the simplest integration — handles token fetch, mount, and cleanup. Start here. |
AssessmentShell | You fetch the assessment yourself and want full control over data flow + submission. |
EdpireQuestion | You’re building a Custom Flow (question-by-question, Duolingo-style). |
import { EdpireAssessmentPlayer, AssessmentShell, EdpireQuestion } from "@edpire/sdk/react"
All components require React 19. CSS is bundled and injected automatically — no stylesheet imports needed.
EdpireAssessmentPlayer
The recommended way to embed a player in React. Wraps EdpireAssessment.mount() and handles token fetching, mounting, unmounting, and StrictMode double-invoke cleanup — the boilerplate you’d otherwise write with useRef + useEffect.
import { EdpireAssessmentPlayer } from "@edpire/sdk/react"
function LessonPage({ assessmentId }: { assessmentId: string }) {
return (
<EdpireAssessmentPlayer
tokenEndpoint="/api/edpire/token"
assessmentId={assessmentId}
onComplete={(r) => console.log("Score:", r.score, "/", r.max_score)}
onError={(e) => console.error(e.code, e.message)}
style={{ width: "100%", height: "100vh" }}
/>
)
}
Pair this with createEdpireTokenHandler() on the server — see Embedded Player → Quickstart.
EdpireAssessmentPlayerProps
| Prop | Type | Required | Description |
|---|
tokenEndpoint | string | Yes | URL of your server endpoint that returns { token }. The component POSTs { assessmentId } here. |
assessmentId | string | Yes | Assessment UUID to embed. Sent to tokenEndpoint in the POST body. |
tokenBody | Record<string, unknown> | No | Extra fields merged into the POST body (e.g. course ID for server-side validation). |
onComplete | (result: EmbedResult) => void | No | Called on successful submission. |
onError | (error: EmbedError) => void | No | Called on non-recoverable errors (token failure, origin blocked, etc.). |
locale | "en" | "fr" | "ar" | No | UI language. RTL applied automatically for "ar". |
branding | TakeBranding | No | Override org branding (white-labelling). |
returnUrl | string | No | Post-submit redirect URL with score params appended. |
reportLabel | string | No | Custom label for the report button. |
onBack | () => void | No | Back button handler (shown before submission). |
backLabel | string | No | Custom back button label. |
overlayConfig | OverlayConfig | No | Customise the grading overlay. |
mediaHandler | QuestionRuntimeProps["mediaHandler"] | No | Required for file-upload/recording OpenResponse questions. |
baseUrl | string | No | Edpire instance URL. Defaults to https://edpire.com. |
className | string | No | CSS class for the container <div>. Use this to size the player (e.g. "h-screen"). |
style | React.CSSProperties | No | Inline style for the container. Defaults to { width: "100%", height: "100%" } when className is absent. |
The player fills its container — you control the size via className or style.
These are React 19 components. CSS is bundled and injected automatically — no stylesheet imports needed.
AssessmentShell
The complete assessment-taking UI: sticky top bar with logo + countdown, exercise sections with score badges, submit confirmation, grading overlay, post-submit score banner, and per-question feedback. You supply the assessment data and an onSubmit handler that does the grading.
import { AssessmentShell } from "@edpire/sdk/react"
import type { SubmitResult } from "@edpire/sdk/react"
function Player({ assessment, meta }) {
async function handleSubmit(answers): Promise<SubmitResult> {
const res = await fetch("/api/grade", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ answers }),
})
if (!res.ok) throw new Error("Grading failed")
return res.json()
}
return (
<AssessmentShell
assessment={assessment}
meta={{ title: "Unit 3 Quiz", category: "Grammar" }}
locale="en"
onSubmit={handleSubmit}
/>
)
}
What your /api/grade server endpoint must return. client.submit() returns GradeResult (snake_case); AssessmentShell expects SubmitResult (camelCase). Map between them on the server:
// POST /api/grade/route.ts
import { EdpireClient } from "@edpire/sdk/client"
const client = new EdpireClient({ apiKey: process.env.EDPIRE_API_KEY! })
export async function POST(req: Request) {
const { assessmentId, answers } = await req.json()
const result = await client.submit(assessmentId, {
learner_ref: session.userId,
answers,
})
// Map GradeResult (snake_case) → SubmitResult (camelCase)
return Response.json({
submissionId: result.submission_id,
totalScore: result.score,
maxScore: result.max_score,
percentage: result.percentage,
passed: result.passed,
exerciseFeedback: [], // GradeResult doesn't include per-node feedback;
// the score banner shows, but inline corrections won't.
awaitingManualGrading: false,
})
}
client.submit() returns per-question scores (exercise_results) but not the per-node visual feedback (exerciseFeedback) that AssessmentShell uses to highlight incorrect answers inline. If you need both, use the Embedded Player instead — it gets the full response from Edpire automatically.
When to use AssessmentShell vs EdpireAssessmentPlayer: EdpireAssessmentPlayer fetches assessment data client-side after mount and handles everything for you — start there. Drop down to AssessmentShell only when you need to control data loading yourself: server-rendering the assessment for a faster initial paint, pre-fetching it before the learner navigates, or plugging into your own caching layer. AssessmentShell expects data in the runtime shape (AssessmentContent), not the raw REST Assessment — you’re responsible for that conversion.
AssessmentShellProps
| Prop | Type | Required | Description |
|---|
assessment | AssessmentContent | Yes | The assessment content (runtime shape) to render. |
meta | { title: string; category: string | null } | Yes | Title + category shown in the top bar. |
onSubmit | (answers) => Promise<SubmitResult> | Yes | Grades the attempt. Should throw on failure (the shell shows an error banner). |
locale | "en" | "fr" | "ar" | No | Shell string language. Default "en". RTL auto-applied for "ar". |
branding | TakeBranding | No | Logo, colors, fonts. See Branding. |
timeLimitMinutes | number | null | No | Shows a countdown chip; auto-submits at 0:00. |
startedAt | string (ISO) | No | ISO timestamp when the attempt started. If provided, the countdown picks up from where it left off after a page refresh instead of restarting from the full limit. |
onViewReport | (result: SubmitResult) => void | No | Called when the learner clicks “View Full Report”. Omit to hide the button. |
reportLabel | string | No | Custom label for the report button. |
onBack | () => void | No | Called when the learner clicks the Back button in the top bar (visible only before submission). Use it to navigate back in your app or close a modal. |
backLabel | string | No | Custom back-button label (default: localized “Back”). |
allowReset | boolean | No | Show a Reset button in the top bar. |
onReset | () => void | No | Reset handler. |
onClose | () => void | No | Show a close (✕) button and handle it. |
overlayConfig | OverlayConfig | No | Customise the grading overlay. See Branding. |
contained | boolean | No | Scope the grading and time-up overlays to the shell’s bounding box instead of the full viewport. Use when the player is embedded in a page alongside other content, rather than filling the full screen. |
mediaHandler | QuestionRuntimeProps["mediaHandler"] | No | Required for file-upload/recording OpenResponse questions. See mediaHandler example. |
fallbackLogoUrl | string | No | Logo shown when branding.logoUrl is absent. Defaults to an inline Edpire SVG. |
SubmitResult (returned by onSubmit)
interface SubmitResult {
submissionId: string
totalScore: number
maxScore: number
percentage: number
passed: boolean
/** If null/undefined, the "View Full Report" button is hidden. */
returnUrl?: string | null
exerciseFeedback: ExerciseFeedbackData[]
/** When true, score banners hide the score (still under review). */
awaitingManualGrading?: boolean
}
exerciseFeedback drives the inline per-question feedback. See the type catalog for ExerciseFeedbackData, QuestionFeedback, and NodeFeedback.
EdpireQuestion
Renders one question and reports answer changes. The core building block for Custom Flow.
import { EdpireQuestion } from "@edpire/sdk/react"
import type { RuntimeAnswer } from "@edpire/sdk/react"
function QuestionStep({ step }) {
const [answers, setAnswers] = useState<RuntimeAnswer[]>([])
const [feedback, setFeedback] = useState<Record<string, unknown> | null>(null)
return (
<EdpireQuestion
content={step.content} // ContentAST from flattenAssessment()
onAnswersChange={setAnswers}
feedback={feedback} // null = no feedback; pass /check result to show it
dir="ltr" // "rtl" for Arabic
/>
)
}
EdpireQuestionProps
| Prop | Type | Required | Description |
|---|
content | unknown | Yes | The question’s content_ast (a FlatStep.content). |
onAnswersChange | (answers: RuntimeAnswer[]) => void | No | Fires on every answer change. Collect these for /check and submit. |
feedback | Record<string, unknown> | null | No | Pass the feedback field from POST /check directly — the component converts it internally. null/undefined clears feedback. |
initialAnswers | RuntimeAnswer[] | No | Pre-fill answers (e.g. restoring state when navigating back). |
className | string | No | Class added to the question root <div>. |
dir | "ltr" | "rtl" | No | Text direction. Default "ltr". |
feedback takes the raw JSON from the /check endpoint — no manual mapping. Internally the component converts it to the Map the runtime expects.
GradingOverlay
The branded spinner overlay shown while grading. You don’t usually render it directly — AssessmentShell and the Embedded Player show it for you and accept an overlayConfig. The OverlayConfig / SpinnerTheme types and theming options are documented under Branding → Grading overlay.
Working examples
See the starter templates for complete, runnable integrations:
npx --package=@edpire/sdk create-edpire-app nextjs my-app # EdpireAssessmentPlayer + token handler
npx --package=@edpire/sdk create-edpire-app vite-express my-app # same, with Vite + Express
For a Custom Flow (question-by-question), see the Custom Flow guide.