부분 API 응답 병합 시 모의고사 상태 유지

이 글의 목차 7개 항목

문제와 원인

문항 조회 응답에는 새 문항과 빈 기록 배열 placeholder가 함께 들어왔다. 기존 병합은 [] !== undefined를 만족한 attempts·examSessions·evaluations를 그대로 채택해 브라우저의 풀이·시험·평가 상태를 지웠다.

수정 전 코드

refreshData

(previous) => ({
  ...previous,
  ...payload,
  questions: mergeById(previous.questions, payload.questions ?? []),
  theories: mergeById(previous.theories, payload.theories ?? []),
  attempts: payload.attempts !== undefined
    ? payload.attempts : previous.attempts,
  examSessions: payload.examSessions !== undefined
    ? payload.examSessions : previous.examSessions,
  theoryProgress: payload.theoryProgress !== undefined
    ? payload.theoryProgress : previous.theoryProgress,
  evaluations: payload.evaluations !== undefined
    ? payload.evaluations : previous.evaluations,
  overview: payload.overview ?? previous.overview,
  practiceMeta: payload.practiceMeta ?? previous.practiceMeta,
  theoryNavigation: payload.theoryNavigation ?? previous.theoryNavigation,
})

수정 후 코드

refreshData

응답의 scope를 병합 함수에 넘겨 해당 조회가 갱신하는 필드만 반영한다. practice·questions는 문항만 병합하고 기존 기록을 유지한다. mock의 시험 목록과 records의 풀이·평가 목록처럼 실제 갱신 대상인 배열은 빈 응답도 반영한다.

const payload = await fetchStudyData(scope, params);
setData(previous => mergeStudyResponse(previous, payload, scope));

mergeStudyResponse

export function mergeStudyResponse(
  previous: StudyData,
  payload: StudyData,
  scope: StudyScope,
): StudyData {
  const common = {
    ...previous,
    settings: payload.settings ?? previous.settings,
    site: payload.site ?? previous.site,
    adminAccess: payload.adminAccess ?? previous.adminAccess,
  };
  const questions = () => mergeById(previous.questions, payload.questions ?? []);

  switch (scope) {
    case "shell": return common;
    case "overview":
    case "bootstrap":
      return { ...common, overview: payload.overview ?? previous.overview };
    case "theory":
    case "theories":
      return {
        ...common,
        theories: mergeById(previous.theories, payload.theories ?? []),
        theoryNavigation: payload.theoryNavigation ?? previous.theoryNavigation,
      };
    case "practice-meta":
      return { ...common, practiceMeta: payload.practiceMeta ?? previous.practiceMeta };
    case "practice":
    case "questions":
      return { ...common, questions: questions() };
    case "records":
      return {
        ...common,
        questions: questions(),
        attempts: payload.attempts,
        examSessions: mergeById(previous.examSessions, payload.examSessions),
        evaluations: payload.evaluations,
        theoryProgress: payload.theoryProgress,
        recordStats: payload.recordStats,
        recordsSummary: payload.recordsSummary,
        recordsPagination: payload.recordsPagination,
      };
    case "mock":
      return {
        ...common, questions: questions(), examSessions: payload.examSessions,
        mockPagination: payload.mockPagination,
      };
    case "mock-session":
      return {
        ...common, questions: questions(),
        examSessions: mergeById(previous.examSessions, payload.examSessions),
      };
  }
}

검증 결과

시험·풀이·평가가 각각 1개, 기존 문항이 1개인 이전 상태에 새 문항 1개와 빈 기록 배열을 가진 동일한 questions 응답을 적용했다.

비교 항목기대 결과수정 전수정 후
examSessions 보존1개0개1개
attempts 보존1개0개1개
evaluations 보존1개0개1개
기존 문항·새 문항 병합2개2개2개

같은 이전 상태와 questions 응답을 로컬 Node의 원본 전후 병합 함수에 다시 1회씩 전달했다. 응시·풀이·평가 배열은 각각 0개에서 1개 보존으로 바뀌었고 문항은 두 구현 모두 2개로 병합됐다.

응시·풀이·평가 각 1개 보존과 문항 2개 병합원본 보기 (새 창)

응시·풀이·평가 각 1개 보존과 문항 2개 병합

100%원본 보기 (새 창)

확대한 이미지는 상하좌우로 이동할 수 있습니다. 키보드는 이미지 영역에 초점을 두고 방향키를 사용하세요. Esc로 닫습니다.