[REFACTOR] 퀴즈 등록 모달 메타 로드를 useEffect 페칭→useTransition으로 개선 #872#925
Conversation
- 강의 선택 시 메타(섹션 주차·takenWeeks) 조회를 반응형 useEffect([courseId])에서 '연결 강의' onChange 핸들러의 useTransition 호출로 이동 (렌더 워터폴 제거, §4) - 취소 안전성: transition엔 cleanup 취소가 없어 metaReqRef로 stale 응답 무시로 대체 (빠른 강의 전환 시 옛 응답이 새 선택을 덮지 않게) - presetCourseId(개별 강의 페이지·강의 고정)만 마운트 1회 로드로 축소 - metaLoading에 isPending 통합, 에러 토스트(§0.1)·1주1퀴즈 중복차단 동작 보존 - 회귀 테스트 18/18 통과 (presetCourseId·adminMeta 라우팅·에러토스트·주차소진)
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Review limit reached
Next review available in: 56 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthrough
Changes퀴즈 메타 로딩 흐름
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant QuizFormModal
participant QuizFormMetaAction
participant Toast
User->>QuizFormModal: 연결 강의 선택
QuizFormModal->>QuizFormMetaAction: loadMeta(cid)
QuizFormMetaAction-->>QuizFormModal: 주차 메타 응답
QuizFormModal-->>User: 메타 및 로딩 상태 갱신
QuizFormMetaAction-->>QuizFormModal: 오류 응답
QuizFormModal->>Toast: 오류 토스트 표시
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@hard-click-frontend/src/features/quizzes/components/QuizFormModal.tsx`:
- Around line 164-167: QuizFormModal의 presetCourseId 메타 조회 effect에서 언마운트 시 진행 중인
요청이 결과를 반영하지 않도록 정리하세요. 가능하면 고정 강의 메타를 서버에서 조회해 props로 전달하고, effect를 유지한다면
cleanup에서 요청 번호를 증가시켜 loadMeta의 성공·실패 처리와 토스트가 무효화된 요청에 대해 실행되지 않도록 하세요.
- Around line 138-160: Update loadMeta to track pending state for the current
reqId rather than relying solely on the shared isMetaPending transition state.
Mark the active request as pending when it starts, and clear loading only when
that same request resolves or rejects; stale responses must not change the
current request’s loading state. Use the existing metaReqRef and metadata
loading state so an earlier course request cannot keep the current course’s week
selector disabled.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: d337c750-e1df-40f3-9b7c-642766cdbbf8
📒 Files selected for processing (1)
hard-click-frontend/src/features/quizzes/components/QuizFormModal.tsx
| const [isMetaPending, startMetaTransition] = useTransition(); | ||
| // 강의 선택 시 실제 섹션(주차)·이미 쓴 주차를 서버에서 로드. useEffect 반응형 페칭(렌더 워터폴) 대신 | ||
| // 이벤트 핸들러('연결 강의' onChange)에서 useTransition으로 호출(§4). transition엔 cleanup 취소가 | ||
| // 없어, 취소 안전성은 metaReqRef로 stale 응답 무시로 대체(빠른 강의 전환 시 옛 응답이 새 선택을 덮지 않게). | ||
| const loadMeta = (cid: number) => { | ||
| if (mode !== 'create' || cid <= 0) return; | ||
| const reqId = ++metaReqRef.current; | ||
| // 관리자(adminMeta)는 소유자무관 관리자 목록으로 takenWeeks 집계 — | ||
| // 강사 엔드포인트(/api/instructor/quizzes)는 로그인 관리자 소유 퀴즈만 반환(0개)이라 1주1퀴즈 중복차단이 안 됨. | ||
| const metaAction = adminMeta | ||
| ? getAdminQuizFormMetaAction | ||
| : getQuizFormMetaAction; | ||
| metaAction(courseId) | ||
| .then((m) => { | ||
| if (!cancelled) setMeta({ courseId, ...m }); | ||
| }) | ||
| .catch(() => { | ||
| startMetaTransition(async () => { | ||
| try { | ||
| const m = await metaAction(cid); | ||
| if (metaReqRef.current === reqId) setMeta({ courseId: cid, ...m }); | ||
| } catch { | ||
| // 실패를 빈 주차로 폴백하면 '등록 가능한 주차가 없습니다'로 오표시돼(§0.1), | ||
| // 조용히 삼키지 않고 사용자에게 알린다. 주차 드롭다운은 로딩 상태로 남아 오등록을 막는다. | ||
| if (!cancelled) | ||
| if (metaReqRef.current === reqId) | ||
| toast.error('주차 정보를 불러오지 못했습니다. 잠시 후 다시 시도해주세요.'); | ||
| }); | ||
| return () => { | ||
| cancelled = true; | ||
| }; | ||
| }, [courseId, mode, adminMeta]); | ||
| // 로딩 = 선택 강의 기준 meta가 아직 안 옴 (파생, 동기 setState 없음). | ||
| } | ||
| }); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="hard-click-frontend/src/features/quizzes/components/QuizFormModal.tsx"
echo "== outline =="
ast-grep outline "$file" --view expanded || true
echo
echo "== relevant lines =="
nl -ba "$file" | sed -n '110,210p'Repository: Hard-Click/Hard-Click-FrontEnd
Length of output: 528
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="hard-click-frontend/src/features/quizzes/components/QuizFormModal.tsx"
echo "== lines 130-185 =="
sed -n '130,185p' "$file"
echo
echo "== search for metaLoading / metaReqRef / useTransition =="
grep -nE 'useTransition|metaReqRef|metaLoading|loadMeta|startMetaTransition|isMetaPending' "$file" || trueRepository: Hard-Click/Hard-Click-FrontEnd
Length of output: 3398
🌐 Web query:
React useTransition async callback pending promise ignored startTransition docs
💡 Result:
In React 19 and later, the useTransition hook has been improved to automatically handle state updates that occur after an await within an async transition [1]. If you are using the startTransition function returned by useTransition, you no longer need to manually wrap state updates that follow an asynchronous request in an additional startTransition call [1]. These updates are automatically included in the transition [1]. However, this improvement applies specifically to the startTransition provided by the useTransition hook [1]. If you are using the standalone React.startTransition function (often used outside of components or where useTransition is unavailable), you must still manually wrap any state updates that occur after an await in a new startTransition call [2][1]. The requirement to wrap updates in earlier versions of React or when using standalone startTransition arises because React loses the asynchronous context when an await expression is encountered [3]. Without this context, React cannot automatically associate subsequent state updates with the original transition, causing them to be treated as standard, urgent updates instead of non-blocking transitions [4][3]. Summary of Behavior: 1. useTransition.startTransition: Supports async functions natively. State updates after await are automatically part of the transition [1]. 2. React.startTransition (standalone): Does not automatically track context after await. State updates after await must be wrapped in another startTransition [2][1]. If you find that your state updates after an await are not being treated as transitions, ensure you are utilizing the startTransition returned by useTransition and check that you are not accidentally relying on the standalone version [2][1].
Citations:
- 1: docs: update startTransition async guidance for React 19 reactjs/react.dev#8496
- 2: https://react.dev/reference/react/startTransition
- 3: https://stackoverflow.com/questions/79923964/react-19-usetransition-behaves-weirdly-with-multiple-async-updates-to-state
- 4: Initial (client-only) async actions support react/react#26621
로딩 상태를 현재 요청 기준으로 분리하세요. isMetaPending만 쓰면 이전 강의의 느린 요청이 끝날 때까지 주차 선택이 계속 비활성화됩니다. reqId로 현재 요청의 pending을 따로 추적하고, 해당 요청이 끝날 때만 로딩을 해제하세요.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@hard-click-frontend/src/features/quizzes/components/QuizFormModal.tsx` around
lines 138 - 160, Update loadMeta to track pending state for the current reqId
rather than relying solely on the shared isMetaPending transition state. Mark
the active request as pending when it starts, and clear loading only when that
same request resolves or rejects; stale responses must not change the current
request’s loading state. Use the existing metaReqRef and metadata loading state
so an earlier course request cannot keep the current course’s week selector
disabled.
- metaLoading이 isMetaPending을 OR로 참조해, 빠른 강의 전환으로 무효화된 stale transition이 아직 pending이면 현재 선택 로드 후에도 주차 드롭다운이 계속 잠기던 회귀 수정 - 파생 조건(meta.courseId !== courseId)만으로 로딩 판별 — 더 정확하고 stale 커플링 없음 - 코드리뷰(적대적 검증) 반영, 회귀 테스트 18/18 유지
There was a problem hiding this comment.
♻️ Duplicate comments (1)
hard-click-frontend/src/features/quizzes/components/QuizFormModal.tsx (1)
164-167: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win클라이언트 렌더링 시점의 데이터 페칭 패턴 개선 및 언마운트 처리 추가
useEffect를 통한 클라이언트 데이터 페칭은 워터폴을 유발하므로, 서버 컴포넌트에서presetCourseId에 해당하는 메타데이터를 사전 조회하여 props로 주입하는 패턴을 권장합니다.
부득이하게 현행 로딩 방식을 유지해야 한다면, 컴포넌트 언마운트 시 진행 중인 비동기 요청이 백그라운드에서 완료되어 불필요한 에러 토스트가 노출되거나 상태가 업데이트되는 것을 막기 위해 반드시cleanup함수를 추가해야 합니다.As per path instructions,
useEffect + useState 데이터 페칭을 지적한다(서버 조회 권장).🛠 제안하는 수정안
useEffect(() => { if (presetCourseId) loadMeta(presetCourseId); + return () => { + metaReqRef.current++; + }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@hard-click-frontend/src/features/quizzes/components/QuizFormModal.tsx` around lines 164 - 167, Update the QuizFormModal data-loading flow around the useEffect that calls loadMeta so presetCourseId metadata is fetched in the server component and passed into the modal as props, removing the client-side fetch/state initialization where possible. If client fetching must remain, add effect cleanup and guard the loadMeta completion/error handling so unmounted components cannot update state or show error toasts.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@hard-click-frontend/src/features/quizzes/components/QuizFormModal.tsx`:
- Around line 164-167: Update the QuizFormModal data-loading flow around the
useEffect that calls loadMeta so presetCourseId metadata is fetched in the
server component and passed into the modal as props, removing the client-side
fetch/state initialization where possible. If client fetching must remain, add
effect cleanup and guard the loadMeta completion/error handling so unmounted
components cannot update state or show error toasts.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 51e809cc-2f5c-4f15-a8f0-962d7d47212d
📒 Files selected for processing (1)
hard-click-frontend/src/features/quizzes/components/QuizFormModal.tsx
- 모달이 닫힌 뒤 진행 중이던 preset 메타 로드의 stale 응답이 에러 토스트를 띄우거나 setMeta를 호출하는 것 방지 — cleanup에서 metaReqRef 증가로 무효화 (기존 useEffect cleanup의 cancelled 가드를 언마운트 경로에서 복원) - CodeRabbit 리뷰 반영, 회귀 테스트 18/18 유지
📋 PR 타입
🙋 관련 역할
공통 (강사·관리자 퀴즈 등록 모달)
📝 작업 내용
useEffect([courseId])→ '연결 강의' onChange 핸들러의useTransition호출로 이동 (렌더링 워터폴 제거, §4/CodeRabbit path instruction)metaReqRef(요청 id)로 stale 응답 무시로 대체 — 빠른 강의 전환 시 옛 응답이 새 선택을 덮지 않게metaLoading에isPending통합, 에러 토스트(§0.1)·1주1퀴즈 중복차단 동작 보존✅ 작업 체크리스트
🔗 연관 이슈
Closes #872
📸 스크린샷 (선택)
UI 변경 없음 (데이터 페칭 구조만 변경, 동작 보존).
💬 리뷰 포인트
presetCourseId트리거는 이슈의 두 안(서버 props 선주입 vs 최소 트리거) 중 최소 마운트 트리거 채택 — 반응형 워터폴 제거가 목적이고 서버 props안은 동적 강의 선택 UX와 부딪혀서(이슈 참고).metaReqRefstale-guard가 기존cancelled플래그의 취소 안전성을 대체하는지.📝 추가 메모
next build성공 · tsc 0 · lint 0Summary by CodeRabbit