Quiz Store
Zustand store managing quiz feature state
Quiz Store
The quiz store is a Zustand store that manages all state for the Quiz Generator feature. It handles video URLs, video metadata, quiz questions, user answers, AI model selection, UI states, and usage tracking.
Store Implementation
import { create } from 'zustand';
export interface QuizQuestion {
id: string;
question: string;
options: string[];
correctAnswer: number;
explanation?: string;
}
export interface UserAnswer {
questionId: string;
selectedOption: number;
}
export interface VideoDetails {
id: string;
title: string;
description: string;
thumbnails: {
maxres?: { url: string };
high?: { url: string };
medium?: { url: string };
};
channelTitle: string;
publishedAt: string;
duration: number;
viewCount: number;
likeCount: number;
}
export interface UsageInfo {
limit: number;
remaining: number;
reset: number;
used: number;
}
interface QuizState {
videoUrl: string;
videoDetails: VideoDetails | null;
quiz: QuizQuestion[];
userAnswers: UserAnswer[];
selectedLLM: string;
numQuestions: string;
showFullDescription: boolean;
isLoading: boolean;
isGenerating: boolean;
isSubmitted: boolean;
isSuccess: boolean;
score: number;
hasCopied: boolean;
usage: UsageInfo | null;
}
interface QuizActions {
setVideoUrl: (url: string) => void;
setVideoDetails: (details: VideoDetails | null) => void;
setQuiz: (quiz: QuizQuestion[]) => void;
setUserAnswers: (answers: UserAnswer[]) => void;
setSelectedLLM: (model: string) => void;
setNumQuestions: (num: string) => void;
setShowFullDescription: (show: boolean) => void;
setIsLoading: (loading: boolean) => void;
setIsGenerating: (generating: boolean) => void;
setIsSubmitted: (submitted: boolean) => void;
setIsSuccess: (success: boolean) => void;
setScore: (score: number) => void;
setHasCopied: (copied: boolean) => void;
setUsage: (usage: UsageInfo | null) => void;
updateUserAnswer: (questionId: string, selectedOption: number) => void;
resetQuiz: () => void;
reset: () => void;
}
const initialState: QuizState = {
videoUrl: '',
videoDetails: null,
quiz: [],
userAnswers: [],
selectedLLM: 'gemini-2.5-flash',
numQuestions: '5',
showFullDescription: false,
isLoading: false,
isGenerating: false,
isSubmitted: false,
isSuccess: false,
score: 0,
hasCopied: false,
usage: null
};
export const useQuizStore = create<QuizState & QuizActions>((set) => ({
...initialState,
setVideoUrl: (url) => set({ videoUrl: url }),
setVideoDetails: (details) => set({ videoDetails: details }),
setQuiz: (quiz) => set({ quiz }),
setUserAnswers: (answers) => set({ userAnswers: answers }),
setSelectedLLM: (model) => set({ selectedLLM: model }),
setNumQuestions: (num) => set({ numQuestions: num }),
setShowFullDescription: (show) => set({ showFullDescription: show }),
setIsLoading: (loading) => set({ isLoading: loading }),
setIsGenerating: (generating) => set({ isGenerating: generating }),
setIsSubmitted: (submitted) => set({ isSubmitted: submitted }),
setIsSuccess: (success) => set({ isSuccess: success }),
setScore: (score) => set({ score }),
setHasCopied: (copied) => set({ hasCopied: copied }),
setUsage: (usage) => set({ usage }),
updateUserAnswer: (questionId, selectedOption) =>
set((state) => ({
userAnswers: state.userAnswers.map((answer) =>
answer.questionId === questionId
? { ...answer, selectedOption }
: answer
)
})),
resetQuiz: () =>
set({
userAnswers: initialState.userAnswers,
isSubmitted: false,
score: 0
}),
reset: () => set(initialState)
}));State Properties
videoUrl: string
Stores the YouTube video URL entered by the user.
Initial Value: ''
Usage: Updated from input field, used for API requests
videoDetails: VideoDetails | null
Contains video metadata including ID, title, description, thumbnails, channel info, statistics, and publish date.
Initial Value: null
Set After: Successful fetch from /api/videoDetail
quiz: QuizQuestion[]
Array of generated quiz questions. Each question has an ID, question text, 4 options, correct answer index, and optional explanation.
Initial Value: []
Populated From: /api/quiz endpoint response after AI generation
Example Data:
[
{
id: "q1",
question: "What is the main topic of the video?",
options: [
"Programming basics",
"Machine learning",
"Web development",
"Data science"
],
correctAnswer: 2,
explanation: "The video focuses on modern web development techniques."
}
]userAnswers: UserAnswer[]
Array tracking user's selected answer for each question. Initialized with -1 for unanswered questions.
Initial Value: []
Structure: One entry per quiz question
Example Data:
[
{ questionId: "q1", selectedOption: 2 },
{ questionId: "q2", selectedOption: -1 }, // Not answered
{ questionId: "q3", selectedOption: 0 }
]selectedLLM: string
AI model identifier for quiz generation.
Initial Value: 'gemini-2.5-flash'
Options: Currently supports Gemini 2.5 Flash model
numQuestions: string
Number of quiz questions to generate. Stored as string for select component compatibility.
Initial Value: '5'
Valid Range: '2' to '10'
showFullDescription: boolean
Toggle state for expanding/collapsing video description.
Initial Value: false
Usage: Controls line-clamp CSS class on description
isLoading: boolean
Loading state during video and transcript fetching.
Initial Value: false
Set to true: When fetching video details and transcript
Set to false: When fetch completes or errors
isGenerating: boolean
Loading state during AI quiz generation.
Initial Value: false
Set to true: When generating quiz from transcript
Set to false: When generation completes or errors
Usage: Shows quiz skeleton and updates button label to "Generating..."
isSubmitted: boolean
Whether the quiz has been submitted by the user.
Initial Value: false
Set to true: When user clicks "Submit Quiz" button
Effect:
- Locks answer selection
- Shows score badge
- Displays color-coded correct/incorrect indicators
- Shows explanations
- Enables retry button
isSuccess: boolean
Success state for UI feedback animation.
Initial Value: false
Flow:
- Set to
trueafter successful quiz generation - Auto-reset to
falseafter 4 seconds
score: number
Number of correct answers after quiz submission.
Initial Value: 0
Calculated: By comparing userAnswers with quiz[].correctAnswer
Displayed: As badge "Score: X/Y" in quiz header
hasCopied: boolean
Temporary state for copy button feedback.
Initial Value: false
Flow:
- Set to
truewhen copy succeeds - Changes copy icon to checkmark
- Auto-reset to
falseafter 2 seconds
usage: UsageInfo | null
API usage tracking information.
Initial Value: null
Structure:
{
limit: 100, // Total allowed requests
remaining: 75, // Requests remaining
reset: 1696636800000, // Unix timestamp
used: 25 // Requests used
}Actions
setVideoUrl(url: string)
Updates the video URL from user input.
Called From: Input field onChange handler
setVideoDetails(details: VideoDetails | null)
Stores video metadata after API fetch.
Called From: useFetchVideoAndTranscript hook
setQuiz(quiz: QuizQuestion[])
Stores generated quiz questions.
Called From: useGenerateQuiz hook after parsing AI response
setUserAnswers(answers: UserAnswer[])
Sets the complete user answers array. Used for initialization and reset.
Called From:
useGenerateQuiz- Initialize empty answersuseQuizActions.handleRetry- Reset to empty answers
setSelectedLLM(model: string)
Updates the AI model selection.
Called From: LLM selector dropdown (if implemented)
setNumQuestions(num: string)
Updates the number of questions to generate.
Called From: Select component onValueChange
setShowFullDescription(show: boolean)
Toggles video description expansion.
Called From: Show more/less button click handler
setIsLoading(loading: boolean)
Controls loading state for video/transcript fetch.
Called From: useFetchVideoAndTranscript hook
setIsGenerating(generating: boolean)
Controls loading state for quiz generation.
Called From: useGenerateQuiz hook
setIsSubmitted(submitted: boolean)
Marks quiz as submitted.
Called From:
useQuizActions.handleQuizSubmit- Set totrueuseQuizActions.handleRetry- Set tofalse
setIsSuccess(success: boolean)
Controls success feedback animation.
Called From: useFetchVideoAndTranscript hook
setScore(score: number)
Updates the quiz score.
Called From:
useQuizActions.handleQuizSubmit- Calculate and set scoreuseQuizActions.handleRetry- Reset to 0
setHasCopied(copied: boolean)
Controls copy button feedback.
Called From: useQuizActions.handleCopy
setUsage(usage: UsageInfo | null)
Updates API usage information.
Called From: useFetchUsage hook
updateUserAnswer(questionId: string, selectedOption: number)
Updates a single user answer while preserving others.
Implementation:
updateUserAnswer: (questionId, selectedOption) =>
set((state) => ({
userAnswers: state.userAnswers.map((answer) =>
answer.questionId === questionId
? { ...answer, selectedOption }
: answer
)
}))Called From: useQuizActions.handleAnswerSelect when user clicks an option
Process: Maps through answers array, updates matching question, returns new array
resetQuiz()
Resets quiz-specific state for retrying without clearing video details.
Resets:
userAnswersto initial empty arrayisSubmittedtofalsescoreto0
Preserves:
- Video details
- Quiz questions
- Video URL
Called From: useQuizActions.handleRetry
reset()
Resets entire store to initial state.
Called From: Navigation away from feature or complete reset
Store Patterns
Computed Values
The store doesn't include computed values. These are calculated in components:
Quiz Completion Check:
const allAnswered = userAnswers.every((answer) => answer.selectedOption !== -1);Individual Answer Correctness:
const isCorrect = userAnswers.find(
(answer) => answer.questionId === question.id
)?.selectedOption === question.correctAnswer;State Organization
The store is organized into logical sections:
- Input State:
videoUrl,numQuestions,selectedLLM - Data State:
videoDetails,quiz,userAnswers,usage - UI State:
showFullDescription,hasCopied - Loading State:
isLoading,isGenerating,isSubmitted,isSuccess - Result State:
score
This separation makes it easy to understand what each part of the state represents and when it should be updated.