Skip to content

[FIX] 수정 모달 플레이 버튼 재클릭 시 상태 토글 안 되는 문제 수정#244

Merged
kimminna merged 3 commits into
developfrom
fix/web/243-play-button-toggle-race-condition
Jul 16, 2026
Merged

[FIX] 수정 모달 플레이 버튼 재클릭 시 상태 토글 안 되는 문제 수정#244
kimminna merged 3 commits into
developfrom
fix/web/243-play-button-toggle-race-condition

Conversation

@jjangminii

Copy link
Copy Markdown
Contributor

ISSUE 🔗

close #243



What is this PR? 🔍

할 일 수정 모달에서 플레이 버튼을 연속으로 클릭하면 두 번째 클릭부터 상태가 반영되지 않고 서버가 409를 반환하던 문제를 수정했습니다.

배경

  • 기존 구조: 수정 모달은 overlay-kitoverlay.open(callback)으로 열리는데, 이 콜백은 모달을 여는 순간에 딱 한 번만 호출되고 모달이 열려있는 동안 다시 호출되지 않습니다.
  • 발생 문제: 모달을 열 때 캡처된 onTogglePlay(부모가 만든 클로저)가 그 시점의 activeTimer를 그대로 고정해서 참조하기 때문에, 첫 클릭으로 타이머 상태가 실제로 바뀌어도(예: RUNNING → PAUSED) 모달의 클릭 핸들러는 계속 예전 상태를 기준으로 액션(PAUSE/RESUME)을 계산했습니다. 화면에 보이는 타이머 상태(뱃지 등)는 DetailTodoModalQueryuseActiveTimer()를 직접 구독해서 최신으로 갱신되지만, 클릭 시 실행되는 액션 로직만 낡은 상태를 참조하는 불일치가 있었습니다.
  • 해결 방향: overlay.open()에 넘기는 콜백에는 매 렌더마다 최신 값으로 갱신되는 ref를 통해 접근하도록 해서, 클릭 시점에는 항상 최신 activeTimer 상태를 반영한 로직이 실행되도록 했습니다. 추가로 startTimer/changeStatus mutation이 진행 중이거나 activeTimer 쿼리가 리페치 중일 때는 재생 버튼 입력 자체를 무시하는 가드도 추가했습니다.

수정 모달 플레이 버튼

  • 변경 요약: DetailTodoModalContainer.tsx에서 overlay.open()에 넘기는 onTogglePlay를 ref로 감싸고, useTodayTodoList.ts/use-home-todos-by-date.tshandlePlay/handleTogglePlay에 진행 중 재클릭 가드를 추가했습니다.
  • 이유: 모달이 열려있는 동안 재생 버튼을 눌러 타이머 상태가 바뀐 뒤 다시 누르면, 모달에 고정된 콜백이 여전히 예전 activeTimer.status로 액션을 계산해 서버에 이미 처리된 상태를 다시 요청(예: 이미 PAUSED인데 PAUSE)해서 409(TIMER_409)가 발생했습니다.
  • 구현 방식: DetailTodoModalContainer에서 onTogglePlayRef = useRef(onTogglePlay)를 두고 매 렌더마다 onTogglePlayRef.current = onTogglePlay로 갱신합니다. overlay.open()에 넘기는 JSX는 onTogglePlay={() => onTogglePlayRef.current()}처럼 ref를 통해 간접 호출하도록 바꿔서, 이 래퍼 함수 자체는 모달 오픈 시점에 고정되어도 실제 호출은 클릭 시점에 ref.current(그 시점의 최신 handlePlay)를 참조하게 됩니다. 또한 useTodayTodoList/use-home-todos-by-dateuseStartTimer/useChangeStatusisPendinguseActiveTimer()isFetching을 조합한 isTimerActionPending을 만들어 handlePlay/handleTogglePlay 진입부에서 가드했습니다.
  • 경계 · 제약: 이전 커밋에서 모달의 재생 버튼을 시각적으로 비활성화하기 위해 isPlayPending prop을 추가했었는데, 이 값도 같은 이유(고정된 콜럭/props)로 모달 안에서는 정확히 갱신될 수 없어 제거했습니다. ref 기반 수정만으로 실제 동작(액션 계산)은 항상 최신 상태를 반영하므로 별도의 시각적 disable 없이도 정확성은 보장됩니다.



To Reviewers

overlay-kit으로 여는 다른 모달/오버레이에도 같은 패턴(모달 오픈 시점에 고정된 콜백이 이후 상태 변화를 반영하지 못하는 문제)이 잠재해 있을 수 있습니다. 이번 PR에서는 재현이 확인된 수정 모달 플레이 버튼만 고쳤고, 다른 오버레이는 범위 밖입니다.



Screenshot 📷



Test Checklist ✔

  • pnpm check-types 통과
  • pnpm lint 통과
  • 사용자가 실제 dev 서버에서 재현 확인 후 리포트 (첫 클릭 PAUSE 성공 → 재클릭 시 409) — 수정 후 재검증은 리뷰어 확인 요청

- overlay로 여는 모달의 onTogglePlay 콜백이 모달을 열 때의 낡은 activeTimer를 계속 참조하던 문제를 ref 기반 콜백으로 해결했습니다
- startTimer/changeStatus 진행 중이거나 activeTimer 리페치 중에는 재생 버튼 클릭을 무시하도록 가드를 추가했습니다
@vercel

vercel Bot commented Jul 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
timo Ready Ready Preview, Comment Jul 16, 2026 3:36am

Request Review

@github-actions github-actions Bot requested review from kimminna and yumin-kim2 July 15, 2026 19:36
@github-actions github-actions Bot added the ⏰ Timo-web Timo 웹 서비스 label Jul 15, 2026
@github-actions github-actions Bot added 🐛 Bug 기능이 정상적으로 작동하지 않는 문제 수정 ♠️ 정민 정민양 labels Jul 15, 2026
- 코드만으로 파악되는 설명 주석을 정리했습니다
@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown

Timo Performance Report

Bundle Size — timo-web
라우트 크기 First Load JS
/[locale]/home 211.54 kB 🔴 417.39 kB
/[locale]/today 195.74 kB 🔴 401.59 kB
/[locale]/focus 160.64 kB 🔴 366.50 kB
/[locale]/settings 167.19 kB 🔴 373.05 kB
/[locale]/statistics 233.45 kB 🔴 439.30 kB
/[locale]/[...rest] 0 B 🟡 205.86 kB
/[locale]/login 212.96 kB 🔴 418.82 kB
/[locale]/oauth/callback 119.66 kB 🟡 325.51 kB
/[locale]/onboarding 233.44 kB 🔴 439.29 kB
/[locale] 118.97 kB 🟡 324.83 kB
/[locale]/policy 125.09 kB 🟡 330.95 kB

공유 번들: 205.86 kB
🟢 < 200kB  |  🟡 < 350kB  |  🔴 ≥ 350kB (First Load JS · gzip)

Lighthouse — timo-web
URL Perf A11y LCP CLS TBT
/en/home 🔴 61 🟢 95 🔴 15.8s 🟢 0.000 🟡 530ms
/en/today 🔴 61 🟢 95 🔴 15.6s 🟢 0.000 🟡 505ms
/en/focus 🔴 59 🟢 95 🔴 15.3s 🟢 0.000 🔴 626ms
/en/statistics 🔴 64 🟢 95 🔴 15.3s 🟢 0.000 🟡 413ms

Perf ≥ 70 / A11y ≥ 85 목표
LCP 🟢 < 2.5s 🟡 < 4s 🔴 ≥ 4s  |  CLS 🟢 < 0.1 🟡 < 0.25 🔴 ≥ 0.25  |  TBT 🟢 < 200ms 🟡 < 600ms 🔴 ≥ 600ms

Image Optimization — timo-web
파일 크기 포맷 상태
favicon.png 27.84 kB PNG ⚠️ 🟢
images/google-calendar.png 36.20 kB PNG ⚠️ 🟢
images/google-logo.png 26.79 kB PNG ⚠️ 🟢

총 3개 · 90.84 kB  |  🟢 < 200KB  |  🟡 < 500KB  |  🔴 ≥ 500KB
⚠️ 3개 파일 WebP/AVIF 변환 권장

측정 커밋: fd1b542

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kimminna, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 17 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bc6d6966-8e8d-476f-997c-3d3994dec07e

📥 Commits

Reviewing files that changed from the base of the PR and between 57901b5 and b455ab4.

📒 Files selected for processing (2)
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/use-home-todos-by-date.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/useTodayTodoList.ts

Walkthrough

홈과 오늘 목록 훅에 타이머 작업 대기 상태와 중복 실행 방지 가드를 추가하고, 상세 모달은 최신 재생 토글 핸들러를 참조하도록 변경했습니다.

Changes

타이머 제어 흐름

Layer / File(s) Summary
타이머 대기 상태와 제어 가드
apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/use-home-todos-by-date.ts, apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/useTodayTodoList.ts
활성 타이머 조회, 타이머 시작, 상태 변경의 비동기 상태를 isTimerActionPending으로 조합하고, 진행 중인 재생·일시정지·재개 처리를 중단하도록 변경했습니다.
상세 모달 핸들러 동기화
apps/timo-web/containers/todo-modal/detail/DetailTodoModalContainer.tsx
onTogglePlay를 ref에 갱신하고, 모달 쿼리에서 최신 콜백을 호출하는 래퍼를 전달하도록 변경했습니다.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: yumin-kim2, kimminna

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed stale 콜백 ref 교체와 진행 중 재클릭 가드로 #243의 재생/일시정지/재개 토글 문제를 해결합니다.
Out of Scope Changes check ✅ Passed 변경 범위가 모달 콜백 갱신과 타이머 중복 클릭 방지에만 집중되어 있어 벗어난 수정은 보이지 않습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed 주요 변경점인 수정 모달 플레이 버튼 재클릭 시 오래된 상태 참조로 토글이 안 되던 문제를 잘 요약합니다.
Description check ✅ Passed 수정 모달의 stale callback 문제와 재클릭 방지 가드 추가라는 변경 내용이 설명과 일치합니다.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/web/243-play-button-toggle-race-condition

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@jjangminii jjangminii changed the title [FIX] 수정 모달 플레이 버튼 연속 클릭 시 상태 토글 안 되는 문제 수정 [FIX] 수정 모달 플레이 버튼 재클릭 시 상태 토글 안 되는 문제 수정 Jul 15, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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 `@apps/timo-web/containers/todo-modal/detail/DetailTodoModalContainer.tsx`:
- Line 6: Replace the render-time ref.current mutation used for the play toggle
with React’s useEffectEvent, and rename the resulting handler to
handleTogglePlay. Update the affected effect and callback dependencies/usages to
invoke handleTogglePlay while preserving the existing toggle behavior and
closure-safety.
🪄 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: 4af30d5e-eae0-44f8-bf2b-a270c1654df0

📥 Commits

Reviewing files that changed from the base of the PR and between dd18e8a and 57901b5.

📒 Files selected for processing (3)
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/home/_hooks/use-home-todos-by-date.ts
  • apps/timo-web/app/[locale]/(main)/(with-time-sidebar)/today/_hooks/useTodayTodoList.ts
  • apps/timo-web/containers/todo-modal/detail/DetailTodoModalContainer.tsx

import { useTranslations } from "next-intl";
import { overlay } from "overlay-kit";
import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

렌더링 중 ref.current 직접 수정을 지양하고 useEffectEvent를 활용해 보세요.

훌륭한 아이디어로 클로저 캡처 문제를 해결하셨네요! 센스 만점입니다. 🎉
다만, React 컴포넌트 렌더링 중에 ref.current를 직접 수정하는 것은 컴포넌트의 순수성을 해칠 수 있어 동시성(Concurrent) 렌더링 환경에서 예기치 않은 동작을 유발할 수 있습니다.

제공된 React 19.2 문서에 따르면, 이러한 경우 안정적인 이벤트 핸들러 참조를 제공하는 useEffectEvent를 사용하도록 권장하고 있습니다. 이를 활용하면 렌더링 중 사이드 이펙트 걱정 없이 코드를 훨씬 깔끔하고 안전하게 유지할 수 있습니다. 또한 코딩 가이드라인의 이벤트 핸들러 네이밍 규칙(handle 접두사)에 맞춰 handleTogglePlay로 네이밍을 조정하면 더 완벽할 것 같습니다. 최신 API를 적용해서 코드를 한 단계 더 업그레이드해 보세요! ✨

참고 문서: React 공식 문서 - useEffectEvent (또는 React 19.2 릴리스 노트)

💡 제안하는 리팩토링 코드
-import { useCallback, useEffect, useRef, useState } from "react";
+import { useCallback, useEffect, useEffectEvent, useState } from "react";

// ...

-  const onTogglePlayRef = useRef(onTogglePlay);
-  onTogglePlayRef.current = onTogglePlay;
+  const handleTogglePlay = useEffectEvent(() => {
+    onTogglePlay();
+  });

// ...

-        onTogglePlay={() => onTogglePlayRef.current()}
+        onTogglePlay={handleTogglePlay}

Also applies to: 156-158, 177-177

🤖 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 `@apps/timo-web/containers/todo-modal/detail/DetailTodoModalContainer.tsx` at
line 6, Replace the render-time ref.current mutation used for the play toggle
with React’s useEffectEvent, and rename the resulting handler to
handleTogglePlay. Update the affected effect and callback dependencies/usages to
invoke handleTogglePlay while preserving the existing toggle behavior and
closure-safety.

Source: Path instructions

@ehye1 ehye1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

단순히 API 중복 호출만 막으면 되는줄 알았는데 overlay.open()이 최초 콜백을 계속 유지하면서 최신 상태를 반영하지 못한 것이 이유였군요.. 모달/portal/overlay를 사용할 경우에는 stale closure를 의심해봐야겠어요
오류 수정 수고하셨습니다!

…to fix/web/243-play-button-toggle-race-condition

@kimminna kimminna left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정상 동작하는 거 확인했습니다!

@kimminna kimminna merged commit 8d810b0 into develop Jul 16, 2026
11 checks passed
@kimminna kimminna deleted the fix/web/243-play-button-toggle-race-condition branch July 16, 2026 03:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⏰ Timo-web Timo 웹 서비스 ♠️ 정민 정민양 🐛 Bug 기능이 정상적으로 작동하지 않는 문제 수정

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FIX] 수정 모달 플레이 버튼 연속 클릭 시 상태 토글 안 되는 문제 수정

3 participants