21. 퀴즈 결과 페이지에는 어떤 버튼이 어떻게 들어가야 할까
2025년 10월 17일
해당 토픽의 문제를 풀다가, 마지막 퀴즈를 풀고 결과 페이지를 확인했을 때, 하단에는 어떤 버튼이 있어야 하는지 고민하였습니다.
현재는 무조건 그냥 다음 이라는 버튼만 있습니다.
하지만 만약 마지막 문제라면, 다음 문제가 없기 때문에 목록으로 되돌아가야 합니다.
이에 버튼을 두개로 나눠야 한다고 생각했습니다.
먼저 목록으로 갈 수 있는 버튼 하나는 무조건 활성화 되어있고, 다음 문제풀기로 가는 버튼은 지금 문제가 마지막 문제인지 확인하고 활성화 여부가 정해져야 합니다.
다만, 현재 api에는 지금 문제가 마지막 문제이지, isLastQuiz 같은 속성은 전달되지 않습니다. 이에, 마지막 문제이닞 여부를 계산하는 로직이 필요합니다.
이 시스템을 구현하는 동시에 리팩터링을 고려하여 관심사 분리, quizNavigationLogic 파일을 따로 분리하였습니다.
quizNavigationLogic.ts
/** * 퀴즈 네비게이션 관련 비즈니스 로직 */ import type { Quiz } from '@/Pages/QuizPage/types'; /** * 현재 퀴즈의 다음 문제 찾기 * @param quizzes - 전체 퀴즈 배열 * @param currentQuizId - 현재 퀴즈 ID * @returns 다음 퀴즈 또는 null */ export const findNextQuiz = (quizzes: Quiz[], currentQuizId: number): Quiz | null => { const currentIndex = quizzes.findIndex((quiz) => quiz.quizId === currentQuizId); if (currentIndex === -1 || currentIndex === quizzes.length - 1) { return null; // 현재 퀴즈를 못 찾거나 마지막 문제 } return quizzes[currentIndex + 1]; }; /** * 다음 안 푼 문제 찾기 * @param quizzes - 전체 퀴즈 배열 * @param currentQuizId - 현재 퀴즈 ID * @returns 다음 안 푼 퀴즈 또는 null */ export const findNextUnsolvedQuiz = (quizzes: Quiz[], currentQuizId: number): Quiz | null => { const currentIndex = quizzes.findIndex((quiz) => quiz.quizId === currentQuizId); if (currentIndex === -1) { return null; // 현재 퀴즈를 못 찾음 } // 현재 문제 다음부터 안 푼 문제 찾기 for (let i = currentIndex + 1; i < quizzes.length; i++) { if (!quizzes[i].isSolved) { return quizzes[i]; } } return null; // 다음 안 푼 문제가 없음 }; /** * 마지막 문제인지 확인 * @param quizzes - 전체 퀴즈 배열 * @param currentQuizId - 현재 퀴즈 ID * @returns 마지막 문제 여부 */ export const isLastQuiz = (quizzes: Quiz[], currentQuizId: number): boolean => { if (quizzes.length === 0) return true; const currentIndex = quizzes.findIndex((quiz) => quiz.quizId === currentQuizId); if (currentIndex === -1) return false; return currentIndex === quizzes.length - 1; }; /** * 현재 퀴즈의 순서 정보 * @param quizzes - 전체 퀴즈 배열 * @param currentQuizId - 현재 퀴즈 ID * @returns { current: number, total: number } */ export const getQuizProgress = ( quizzes: Quiz[], currentQuizId: number, ): { current: number; total: number } => { const currentIndex = quizzes.findIndex((quiz) => quiz.quizId === currentQuizId); return { current: currentIndex === -1 ? 0 : currentIndex + 1, total: quizzes.length, }; }; /** * 다음 문제 버튼 텍스트 결정 * @param hasNextQuiz - 다음 문제 존재 여부 * @param hasNextUnsolvedQuiz - 다음 안 푼 문제 존재 여부 * @returns 버튼 텍스트 */ export const getNextQuizButtonText = ( hasNextQuiz: boolean, hasNextUnsolvedQuiz: boolean, ): string => { if (!hasNextQuiz) { return '완료'; } if (hasNextUnsolvedQuiz) { return '다음 문제'; } return '다음 문제 (풀이 완료)'; }; /** * 다음 문제로 이동할 경로 결정 * @param topicId - 토픽 ID * @param nextQuizId - 다음 퀴즈 ID (없으면 null) * @returns 이동할 경로 */ export const getNextQuizPath = (topicId: number, nextQuizId: number | null): string => { if (nextQuizId === null) { return `/quizList/${topicId}`; // 마지막 문제면 목록으로 } return `/quizSolve/${nextQuizId}`; };
다음과 같이, 퀴즈 네비게이션 관련 비즈니스 로직을 따로 분리하면서 작성하여 추후 테스트 코드 작성이 쉽게 하였습니다.
그리고 이를 이용해, QuizResultPage에서 다음 문제로 가는 로직 및 버튼 활성화 여부를 작성하였습니다.
quizNavigationLogic.ts를 다음과 같이 설정한 이유는,
앞서 말했듯이 api문서가 마지막 문제 여부를 제공해주지 않기 때문에, 어쩔 수 없이 토픽에 대한 퀴즈 배열 api를 호출해야 하고, 이 배열을 기반으로 현재 퀴즈 아이디와 비교하는 방식으로 마지막 문제, 다음문제 찾기 등의 로직을 구현해야 하기 때문에 인자 타입을 Quiz[와 현재 퀴즈 아이디로 설정하였습니다.
QuizResultPage.tsx
이후 QuizResultPage에서 다음과 같이 로직을 구현하였습니다.
const navigate = useNavigate(); const location = useLocation(); const { selectedAnswer, isCorrect, quizData } = location.state as QuizResultState; const { data: quizListData } = useQueryApi<QuizListResponse>( ['topics', quizData?.topicId?.toString() || ''], `/topics/${quizData?.topicId || ''}`, ); if (!quizData) { return ( <Container> <ErrorMessage>퀴즈 결과를 불러올 수 없습니다.</ErrorMessage> </Container> ); } const { questionOrder, questionTitle, difficultyLevel, explanation, questionData, topicId, quizId, } = quizData; const quizzes = quizListData?.quizzes || []; const nextQuiz = findNextQuiz(quizzes, quizId); const handleNextQuestion = () => { const nextPath = getNextQuizPath(topicId, nextQuiz?.quizId || null); navigate(nextPath); }; const handleBackToList = () => { navigate(`/quizList/${topicId}`); };
그리고 이에 맞춰, handleNextQuestion이 null인지 여부에 따라 다음문제 버튼 비활성화 및, 마지막 문제입니다 문구 출현 여부를 설정했습니다.
결과

다음과 같이 마지막 문제면 버튼 비활성화가 됩니다.
테스트코드
테스트코드는 정해진 시나리오에서 순수함수가 정상적으로 작동하는지 목 데이터와 함께 진행했고, 통과할 수 있었습니다.