Callbacks
Most play components accept a callbacks prop — an object of optional functions the host application can register to observe what happens inside the component without owning the rendering. Use it for analytics, host-side state hydration, or kicking off follow-up flows when a user finishes / shares.
Callbacks are fire-and-forget observers — they never block rendering, never receive a return value, and never replace built-in behavior. To override UX (e.g. replace the share button), use the matching CTA prop (shareCTA, additionalCTA, etc.) instead.
Shape per component
Each component exposes a typed callbacks object. The shape is consistent — only the payload type differs based on the component's underlying SDK model.
// ClassicQuizPlay
interface ClassicQuizPlayCallbacks {
onFinish?: (summary: ClassicQuizUserParticipationModel) => void;
onShare?: (summary: ClassicQuizUserParticipationModel) => void;
}
// PollVote
interface PollVoteCallbacks {
onFinish?: (vote: SinglePollVoteModel) => void;
}
// PersonalityQuizPlay
interface PersonalityQuizPlayCallbacks {
onFinish?: (summary: PersonalityQuizParticipationModel) => void;
}
// MatchQuizPlay
interface MatchQuizPlayCallbacks {
onFinish?: (prediction: PredictionResponseModel) => void;
}
// EventGamePlay
interface EventGamePlayCallbacks {
onFinish?: (prediction: EventGamePredictionModel) => void;
}
// EitherOrPlay
interface EitherOrPlayCallbacks {
onFinish?: (participation: EitherOrParticipationModel) => void;
}onFinish
onFinishFires when the user completes a participation in the current session — i.e. their submit / vote / prediction call has just succeeded.
The full SDK participation model (score, picks, points, prediction details, etc.) is passed to the callback so the host can react without re-fetching.
| Component | Fires when |
|---|---|
ClassicQuizPlay | Quiz submitted and summary returned. |
PollVote | Vote submitted successfully. |
PersonalityQuizPlay | Participation submitted and persona match returned. |
MatchQuizPlay | Prediction successfully submitted via sdk.matchQuiz.play. |
EventGamePlay | Prediction successfully submitted via sdk.eventGame.makePrediction / updatePrediction. |
EitherOrPlay | Game reaches the results state (lives exhausted or all pairs played). |
onFinishdoes not fire when the component loads onto an already-finished state (e.g. revisiting a settled game, opening a quiz where max attempts are already reached). It fires only on a fresh participation event in the current session.
Example
import { ClassicQuizPlay } from "fansunited-frontend-components";
<ClassicQuizPlay
{...otherProps}
callbacks={{
onFinish: (summary) => {
analytics.track("quiz_completed", {
quizId: summary.classicQuizId,
correct: summary.correct,
total: summary.classicQuizQuestions,
});
},
}}
/>onShare
onShareClassicQuizPlay only. Fires when the user clicks the default share button — i.e. when no shareCTA override is provided and the built-in navigator.share / Android bridge / clipboard fallback runs.
If you override the share button via shareCTA.onClick / shareCTA.url / shareCTA.component, your own handler is the source of truth — onShare does not fire, because you're already in control. See Share CTA for the override path.
<ClassicQuizPlay
{...otherProps}
callbacks={{
onShare: (summary) => {
analytics.track("quiz_shared", { quizId: summary.classicQuizId });
},
}}
/>Payload models
Each callback receives the SDK participation model as its only argument. The shapes are defined in fansunited-sdk-esm and listed below for reference. Import the type when you need it:
import type {
ClassicQuizUserParticipationModel,
SinglePollVoteModel,
PersonalityQuizParticipationModel,
PredictionResponseModel,
EventGamePredictionModel,
EitherOrParticipationModel,
} from "fansunited-sdk-esm";ClassicQuizUserParticipationModel
ClassicQuizUserParticipationModelPassed to ClassicQuizPlay's onFinish and onShare.
| Field | Type | Description |
|---|---|---|
classicQuizId | string | Quiz entity id. |
classicQuizModel | ClassicQuizBasicModel | Snapshot of the quiz at participation time (title, type, points config, branding, etc.). |
correct | number | Number of correct answers in this attempt. |
classicQuizQuestions | number | Total number of questions in the quiz. |
personalBest | number | The user's best score across all their attempts. |
points | number | Points awarded for this attempt. |
answers | ClassicQuizParticipationQuestionModel[] | Per-question breakdown: { questionId, optionId, correct, correctOptionId }. |
attempts | number | How many times the user has played this quiz. |
SinglePollVoteModel
SinglePollVoteModelPassed to PollVote's onFinish. Extends PollVoteBasicModel.
| Field | Type | Description |
|---|---|---|
id | string | Vote id. |
pollId | string | Poll entity id. |
optionId | string | Single-choice selection. |
optionIds | string[] | All selected option ids (multi-choice polls). |
userId | string | Voter's profile id. |
createdAt | string | ISO timestamp of the vote. |
previousVotes | PreviousPollVoteModel[] | The user's earlier votes on the same poll (one entry per attempt). |
attempts | number | null | Total number of votes the user has cast on this poll. |
PersonalityQuizParticipationModel
PersonalityQuizParticipationModelPassed to PersonalityQuizPlay's onFinish.
| Field | Type | Description |
|---|---|---|
personalityQuizId | string | Quiz entity id. |
userId | string | Player's profile id. |
questions | PersonalityQuizParticipationQuestionModel[] | Per-question selections: { questionId, optionId }. |
personas | PersonalityQuizPersonaPercentsModel[] | Match scores per persona: { personaId, percent }. The persona with the highest percent is the user's primary result. |
attempts | number | How many times the user has played this quiz. |
PredictionResponseModel
PredictionResponseModelPassed to MatchQuizPlay's onFinish.
| Field | Type | Description |
|---|---|---|
id | string | Prediction id. |
gameInstanceId | string | Match quiz entity id. |
gameType | string | Game type identifier. |
userId | string | Player's profile id. |
fixtures | FixturesResponseModel[] | Per-fixture prediction + result: { market, matchId, matchType, matchModel, prediction, result }. |
totalFixtures | number | Number of fixtures in the quiz. |
settledFixtures | number | Number of fixtures already settled (resolved by the backend). |
status | string | Prediction status (active / settled / partial / cancelled, etc.). |
wager | number | Wager amount (if applicable). |
tiebreaker | TiebreakerModel | Tiebreaker selection. |
points | number | Total points earned. |
templateId | string | Template id. |
groupId | string | Group id. |
profileLastUpdatedAt | string | ISO timestamp. |
createdAt | string | ISO timestamp the prediction was first submitted. |
updatedAt | string | ISO timestamp of the latest edit. |
At submit time
fixtures[].resultis empty — results are resolved server-side once the underlying matches finish.
EventGamePredictionModel
EventGamePredictionModelPassed to EventGamePlay's onFinish.
| Field | Type | Description |
|---|---|---|
id | string | Prediction id. |
gameId | string | Event game entity id. |
profileId | string | Player's profile id. |
profileModel | ProfileModel | null | Snapshot of the player's profile. |
status | EventGamePredictionStatusEnum | ACTIVE | WON | LOST | PARTIALLY_WON | CANCELED. |
fixtures | EventGamePredictionFixturesModel[] | Per-fixture prediction: { id, predictionType, prediction, correct, points }. |
tiebreaker | EventGameTiebreaker | Tiebreaker selection. |
gamePoints | number | null | Points from fixture predictions. |
bonusPoints | number | null | Bonus points. |
totalPoints | number | null | gamePoints + bonusPoints. |
position | number | null | Leaderboard position (post-settlement). |
createdAt | string | ISO timestamp the prediction was first submitted. |
updatedAt | string | ISO timestamp of the latest edit. |
At submit time
statusisACTIVEandfixtures[].correct/pointsare unresolved. Settlement values appear after the underlying event is settled.
EitherOrParticipationModel
EitherOrParticipationModelPassed to EitherOrPlay's onFinish.
| Field | Type | Description |
|---|---|---|
instanceId | string | Participation instance id. |
eitherOrId | string | Game entity id. |
gameStartedAt | string | ISO timestamp the participation started. |
currentStep | number | Index of the most recently played pair. |
currentStreak | number | Current correct-answer streak. |
remainingSteps | number | Pairs left to play. At onFinish this is 0 (all pairs played) or the game ended early via lives exhaustion. |
currentLives | number | Lives left. At onFinish this is 0 if the game ended via lives exhaustion. |
currentPoints | number | Points accumulated in this run. |
personalBest | number | The user's best score across all their participations. |
participationCount | number | Total number of times the user has started this game. |
finishedParticipationsCount | number | Of those, how many were completed. |
steps | EitherOrParticipationStepsModel[] | Per-pair detail: { pairId, userSelection, correct, played, timestamp, expired, optionOne, optionTwo }. |
Composition
callbacks is independent of every other prop. You can combine it with CTA overrides, lead capture, theming, etc. on the same component:
<ClassicQuizPlay
{...otherProps}
shareCTA={{ onClick: openCustomShareModal }} // override UX
callbacks={{
onFinish: (summary) => analytics.track("quiz_completed", summary),
// onShare omitted — shareCTA.onClick handles its own analytics
}}
/>Supported components
ClassicQuizPlay, PollVote, PersonalityQuizPlay, MatchQuizPlay, EventGamePlay, EitherOrPlay.
