Skip to content

feat/#118-date-click-race-fix 날짜 연속 클릭 시 선택 상태 롤백/미입력 버그 수정#119

Merged
jaeml06 merged 7 commits intodevelopfrom
feat/#118-date-click-race-fix
Mar 26, 2026
Merged

feat/#118-date-click-race-fix 날짜 연속 클릭 시 선택 상태 롤백/미입력 버그 수정#119
jaeml06 merged 7 commits intodevelopfrom
feat/#118-date-click-race-fix

Conversation

@jaeml06
Copy link
Copy Markdown
Contributor

@jaeml06 jaeml06 commented Mar 23, 2026

🚩 연관 이슈

closed #118

📝 작업 내용

🐛 어떤 문제가 있었나요?

캘린더에서 날짜를 빠르게 연속 클릭하면, 일부 날짜가 선택되지 않거나 이전에 선택한 날짜가 해제되는(롤백) 현상이 발생했습니다.

이 버그는 호스트의 모임 생성 날짜 선택참여자의 투표 날짜 선택 모두에서, 모바일(터치)과 데스크톱(마우스) 환경 모두에서 재현됩니다.

재현 방법

  1. 캘린더에서 날짜 A를 클릭 → 선택됨 ✅
  2. 빠르게 날짜 B를 클릭 → 선택됨 ✅
  3. 빠르게 날짜 C를 클릭 → 날짜 B가 사라지고 C만 선택됨

🔍 원인이 뭐였나요?

ReactDatepickerAdapterhandleMouseUp에서 날짜 토글을 계산할 때 stale closure 문제가 있었습니다.

// ❌ 기존 코드 — selectedDates가 stale closure로 이전 값을 참조
const newSelection = toggleDatesSmart(selectedDates, newRange);
onChange(newSelection);

React의 useState는 비동기적으로 업데이트됩니다. 빠른 연속 클릭 시:

시점 동작 selectedDates (실제) selectedDates (closure)
t1 클릭 A [][A] []
t2 클릭 B [A][A, B] []아직 갱신 안 됨
t3 클릭 C [A, B][A, B, C] []여전히 이전 값

t2에서 selectedDates가 아직 []이므로 toggleDatesSmart([], [B])[B]가 되어 A가 사라집니다.

✅ 어떻게 해결했나요?

React의 updater function 패턴 (setState(prev => ...))을 도입하여, 항상 최신 상태를 기반으로 토글을 계산하도록 변경했습니다.

// ✅ 수정 코드 — React가 최신 상태를 보장
onChange((prevDates) => toggleDatesSmart(prevDates, newRange));

추가로 해결한 문제: 투표 제출 시 stale state

날짜를 선택한 직후 바로 "투표하기" 버튼을 누르면, formattedDates(useMemo)가 아직 갱신되지 않아 마지막 선택이 누락될 수 있었습니다. useRef로 최신 상태를 동기적으로 추적하여 해결했습니다.

📁 변경 파일 요약

파일 변경 내용
host-range-selector/model/types.ts onChange 타입을 updater function 지원으로 확장
host-range-selector/model/useDateSelection.ts updater 패턴 지원 + formattedDatesRef 추가
host-range-selector/ui/ReactDatepickerAdapter.tsx handleMouseUp에서 updater 패턴 사용
meet-create-date/ui/DateSelectPage.tsx 타입 호환
participant-register-date/** 타입 호환 + formattedDatesRef 사용
participant-edit-date/** 타입 호환 + formattedDatesRef 사용

🗣️ 리뷰 요구사항 (선택)

jaeml06 and others added 6 commits March 23, 2026 19:28
날짜 연속 클릭 시 stale closure로 인한 상태 롤백 버그 수정의 기반.
HostRangeSelectorProps.onChange가 Date[] 직접 전달과
(prev: Date[]) => Date[] updater function 모두를 수용하도록 변경.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handleDateChange가 Date[]와 updater function 모두를 수용하도록 확장.
selectedDatesRef/formattedDatesRef를 추가하여 투표 제출 시
stale state 전송을 방지.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
onChange(toggleDatesSmart(selectedDates, newRange))를
onChange((prevDates) => toggleDatesSmart(prevDates, newRange))로 변경.
React가 최신 상태를 보장하므로 빠른 연속 클릭 시 이전 선택이
롤백되는 문제가 해결됨.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- onChange 콜백의 union 타입을 소비자 전체에 전파
- 투표 제출 시 formattedDatesRef.current 사용으로 stale state 방지
- tracking 함수에서 updater function일 때 이벤트 스킵 처리

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- spec.md: 버그 명세 (연속 클릭 시 상태 롤백/미입력)
- plan.md: 구현 계획 (updater 패턴 전환 전략)
- tasks.md: 태스크 목록 및 실행 순서
- checklists/requirements.md: 품질 체크리스트
- CLAUDE.md: agent context 업데이트

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jaeml06 jaeml06 requested a review from yejinleee March 23, 2026 10:45
@jaeml06 jaeml06 self-assigned this Mar 23, 2026
Copy link
Copy Markdown
Contributor

@yejinleee yejinleee left a comment

Choose a reason for hiding this comment

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

코드 리뷰

전체적으로 stale closure 문제 분석과 updater function 패턴 적용이 정확합니다. PR 설명도 훌륭합니다. 한 가지 이슈가 있어서 코멘트 남깁니다.

[Bug] Amplitude 트래킹 이벤트 누락

ReactDatepickerAdapterhandleMouseUp에서 이제 항상 updater function을 전달합니다:

onChange((prevDates) => toggleDatesSmart(prevDates, newRange));

그런데 handleDateChangeWithTracking에서는 typeof dates !== 'function'일 때만 trackEvent를 호출합니다:

const handleDateChangeWithTracking = (
  dates: Date[] | ((prev: Date[]) => Date[]),
) => {
  if (typeof dates !== 'function') {
    trackEvent('host_date_select', { total_days: dates.length });
  }
  handleDateChange(dates);
};

updater function은 항상 typeof === 'function'이므로, 이 PR이 머지되면 host_date_select, voter_date_vote, voter_date_edit 세 이벤트가 모두 발화되지 않습니다.

DateSelectPage.tsx, ParticipantRegisterDatePage.tsx, ParticipantEditDatePage.tsx 세 파일 모두 동일한 문제입니다.

@yejinleee
Copy link
Copy Markdown
Contributor

클로드 야뮤지게 정리하네

유니온 타입 Date[] | ((prev) => Date[])에서 실제로 Date[]가 전달되는
경로가 없어 typeof 분기가 트래킹을 항상 스킵하던 버그를 수정한다.
onChange를 updater function만 받도록 좁혀 불필요한 분기를 제거한다.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jaeml06
Copy link
Copy Markdown
Contributor Author

jaeml06 commented Mar 25, 2026

Amplitude 트래킹 누락 수정 완료

좋은 리뷰 감사합니다! 지적해주신 트래킹 누락 이슈를 수정했습니다.

원인

ReactDatepickerAdapter가 항상 updater function을 전달하는데, handleDateChangeWithTracking에서 typeof dates !== 'function'일 때만 trackEvent를 호출하고 있어 트래킹이 전부 스킵되는 버그였습니다.

수정 방향

단순히 분기를 추가하는 것이 아니라, 근본적으로 onChange 타입을 좁혔습니다.

ReactDatepickerAdapter는 항상 updater function만 전달하고, 직접 Date[]를 전달하는 코드 경로가 없기 때문에 유니온 타입 Date[] | ((prev: Date[]) => Date[])은 사용되지 않는 케이스를 지원하는 과한 추상화였고, 오히려 이번 버그의 원인이 되었습니다.

Before:

// 유니온 타입 → typeof 분기 필요 → Date[] 분기에서만 tracking → updater일 때 누락
const handleDateChangeWithTracking = (
  dates: Date[] | ((prev: Date[]) => Date[]),
) => {
  if (typeof dates !== 'function') {
    trackEvent('host_date_select', { total_days: dates.length });
  }
  handleDateChange(dates);
};

After:

// updater only → 분기 불필요 → 항상 tracking 실행
const handleDateChangeWithTracking = (updater: (prev: Date[]) => Date[]) => {
  handleDateChange((prev) => {
    const next = updater(prev);
    trackEvent('host_date_select', { total_days: next.length });
    return next;
  });
};

변경 파일

  • types.ts: onChange 타입을 (updater: (prev: Date[]) => Date[]) => void로 변경
  • DateSelectPage.tsx, ParticipantRegisterDatePage.tsx, ParticipantEditDatePage.tsx: 분기 제거
  • useParticipantRegisterDate.ts, useParticipantEditDate.ts: onDateClick 시그니처 동일하게 변경
  • useDateSelection.tshandleDateChange는 내부 범용 함수이므로 유니온 유지

@jaeml06 jaeml06 requested a review from yejinleee March 25, 2026 14:50
@jaeml06 jaeml06 merged commit 46b2df4 into develop Mar 26, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] date-click-race-fix

2 participants