Callbacks and results
Handle submitted answers and completed quizzes using the callback payloads shared by the default component and headless API. Learn when events fire, how deferred answers and timeouts affect delivery, and how to interpret and retain results.
Callbacks belong to the engine and work without rendering feedback components. onComplete runs once per completed session, including timeout or early quit. onQuestionSubmit runs once per evaluated immediate attempt, with that attempt's actual answer. In deferred mode it runs when leaving an answered question or completing the quiz (including timeout), once per changed answer revision. Revisiting an unchanged question, rerendering, or changing callback identity does not deliver duplicate events. Callbacks run synchronously after the engine accepts the action; use their payloads rather than a render's captured state.
Payloads
onComplete(summary) and customResultPage(summary) receive a QuestionSummary object:
type QuestionSummary = {
numberOfQuestions: number;
numberOfCorrectAnswers: number;
numberOfIncorrectAnswers: number;
questions: Question[];
userInput: (number | number[] | undefined)[];
totalPoints: number;
correctPoints: number;
timeTaken: number;
};onQuestionSubmit(data) receives the submitted question result:
type QuestionSubmitData = {
question: Question;
userAnswer: number | number[] | undefined;
isCorrect: boolean;
};For userInput and userAnswer, single-answer questions use a 1-based number such as 2, multiple-answer questions use a 1-based number array such as [1, 3], and unanswered questions can be undefined.
summary.questions contains prepared questions after subsetting/shuffling. The incorrect count does not include unanswered questions. Inspect userInput for unanswered entries (undefined or an empty array). On overall or per-question timer expiry or early quit, a partially selected multiple-answer question is evaluated as incorrect and emits a submission event if that selection has not already been committed. Questions with no input remain unanswered.
See Quiz behavior for callback and custom-result snippets.
Delivery order and retained payloads
On completion, pending deferred onQuestionSubmit events run in question order before onComplete. Unanswered questions do not emit a submission event. Immediate-mode attempts have already emitted their events when evaluated, except partial multiple-answer selections, which are evaluated as incorrect at overall or per-question timeout or early quit.
Callback payloads are copied from engine state, so you can retain them or update their arrays without changing the quiz. Treat state.summary and other state returned by hooks as read-only; the copied callback payload guarantee does not apply to state objects.
Callbacks run synchronously and their return values are ignored. Handle failures in your own callbacks and catch rejected promises from async persistence work. A thrown callback error propagates to its caller; it does not undo the accepted quiz action.
A question deadline commits that question before advancing. Final-question expiry also completes the quiz; it does not redeliver an already committed answer. Untouched expired questions emit no answer event.
State and actions
Read quiz status, answer selections, feedback, progress, and results from headless state, then update the session through typed actions. Understand selection and submission rules, deferred feedback, and the reasons an action can be blocked.
TypeScript types
Find the public TypeScript types for quiz data, component props, headless configuration, state, actions, and callback results. Import them directly from the package root and use the engine declarations as a reference for custom integrations.