Skip to content

[config] React 19.3.1 마이그레이션 및 useEffectEvent 도입 - #530

Merged
i-meant-to-be merged 3 commits into
developfrom
config/#529-react-19-migration
Oct 15, 2026
Merged

i-meant-to-be merged 3 commits into
developfrom
config/#529-react-19-migration

Conversation

@i-meant-to-be

@i-meant-to-be i-meant-to-be commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

🚩 연관 이슈

Closes #529

📝 작업 내용

Vite 8 이전 이후 React 18.3.1을 React 19.3.1로 갱신하면서 기존 타이머·청중 공유·투표 동작을 유지함. React 19 타입에서 재현된 ref 초기값·null 호환 오류를 수정하고, 탭 복귀 훅의 최신 콜백 처리만 useEffectEvent로 단순화했음.

의존성과 호환성

  • React·React DOM은 ^19.3.1, 양쪽 타입은 ^19.3.0, Hooks ESLint 플러그인은 ^7.1.1로 선언하고 같은 버전을 설치했음.
  • TypeScript 5.7.3·Vite 8.3.4·Storybook 10.6.2와 다른 직접 의존성의 잠금 버전, Query/MSW 제한과 override를 유지했음. lockfile의 다른 변경은 scheduler·Hooks 플러그인의 전이 의존성과 불필요한 prop-types 타입 제거 및 dev 표식 갱신임.
  • React 19 타입 오류를 먼저 재현한 뒤 LiveShareModal의 복사 타이머 Ref에 undefined 초기값을 주고 TimerPageLogics의 DOM RefObject에 null을 명시했음. 강제 설치·타입 단언으로 우회하지 않았음.
  • createRoot·자동 JSX 변환·Strict Mode·Provider·ErrorBoundary의 오류 수집 책임과 API·소켓 계약은 유지함.

탭 복귀 처리

useDocumentVisibility(onVisible, { enabled, throttleMs })의 공개 계약은 유지함. 예를 들어 청중 화면의 복귀 콜백이 렌더마다 바뀌어도 리스너를 재등록하지 않고, 탭이 다시 보이는 시점에 최신 콜백을 호출함. 기존 콜백 Ref와 동기화 effect를 제거하고 effect가 등록한 visibilitychange 핸들러에서 Effect Event를 호출함.

활성 옵션·재실행 제한 시간·마지막 실행 시각 Ref는 유지하며, enabled 또는 throttleMs 변경만 재구독 조건임. Effect Event를 반환하거나 다른 훅·컴포넌트에 전달하지 않음. Hooks 플러그인은 기존 rules-of-hooks·exhaustive-deps 두 규칙만 명시적으로 활성화했고 Compiler·새 폼 처리·화면 전환·다른 훅의 일괄 변환은 포함하지 않음.

회귀 테스트와 문서

  • 최신 콜백·콜백 변경 시 재등록 없음·옵션 변경과 실행 시각 보존을 검증함.
  • Strict Mode 재실행·재마운트에서 리스너·소켓 구독·timer·Portal 정리를 검증함.
  • 실제 Router·Query Provider로 오류 화면·홈 복구와 Sentry의 렌더 오류 1회 수집·이미 수집된 API 오류의 추가 수집 방지를 검증함.
  • 기존 복사 완료 테스트 한 건은 React 19에서 비동기 화면 반영 전에 단언해 실패했음. findByRole와 비동기 act로 반영을 기다리도록 수정했으며 제품의 표시 시간·동작은 변경하지 않았음.
  • 문서 지도의 React 버전, React UI의 Effect Event 사용 계약과 구조 문서의 참조를 갱신했음. 마이그레이션을 추적하던 임시 문서 future.md는 사용자 요청으로 삭제하고 검증·후속 확인 기록은 이 PR에 보존함.

실제 검증 결과

Windows·Node 24.21.0·npm 11.19.1에서 수행했음. 브라우저 확인은 로컬 Chromium이며 아래 사용자 실제 서버 검증과 구분함.

검증 결과
React 18 기준 엄격한 설치·의존성·546개 테스트·lint·앱/Storybook 빌드·doctor 통과
React 19 엄격한 설치·의존성 최종 manifest·lockfile로 npm ci --engine-strict --strict-peer-deps 및 npm ls --depth=0 통과
React 19 전체 테스트·lint·앱 빌드 61개 파일·555개 테스트, ESLint·Stylelint·타입, 생산 빌드 통과. PR 준비 시 재실행도 통과
Storybook 빌드·doctor·색인 통과. 123개 Story·47개 문서이며 ID 추가·누락 없음
Storybook 개발·정적 양쪽 UpdateModal 12개 play·이미지/스크롤/화면 크기·양방향 args·Actions·Portal 닫기·Docs·청중 모바일 375×812 확인
MSW·청중 시뮬레이터 양쪽 테이블 조회·투표 GET/POST 200·입력/선택/확인/취소 시 보존, 가짜 소켓 SYNC·PLAY·카운트다운·STOP·자유토론·FINISHED 이동 확인
앱 재시작 Storybook 빌드 후 재시작. HTML·진입 모듈·CSS 200, 공개 템플릿의 비회원 타이머·Space·차례 이동·팀 전환·도움말 Esc·영어 홈 확인
실제 개발 서버 사용자가 로그인·저장·사회자/청중 동기화·탭 복귀·재연결·사회자 교체·종료·투표 제출/결과의 시나리오 1~10을 직접 수행하고 모두 정상이라고 보고함
코드 리뷰·문서 새 상·중·하·컨벤션 위반 발견 사항 없음. 코드 형식·문서 링크·분량·diff 검사 통과

단일 투표 Story의 POST 뒤에는 현재 routePattern에서 완료 경로가 ID로 해석돼 잘못된 링크를 표시했음. Story의 API 응답 검증과 앱의 실제 완료 검증을 구분했으며, 앱의 완료·결과 화면은 사용자 시나리오로 확인했음.

관찰 사항과 한계

  • 사용자가 최초 접속에서 Pretendard 미적용 후 새로고침 시 적용된 현상을 보고했음. 이번 폰트 설정 변경은 없고 후속 CDN CSS·woff2 조회는 HTTP 200, font-display는 swap이었음. 발생 당시 Network 기록이 없어 원인과 React 이전 관련성은 미확정이며 수정했다고 주장하지 않음.
  • 투표 모달 취소 후 입력은 보존되지만 포커스는 body로 돌아왔음. useModal 포커스 코드는 변경하지 않았으며 React 18 브라우저 직접 비교·전체 키보드 포커스 복원은 미검증임.
  • 기존 npm 감사 7건(낮음 1·높음 6), ESLint 비권장 버전·설치 스크립트 허용 안내와 큰 묶음 경고·Rolldown 안내는 남아 있음. 최대 앱 JS는 949.85 → 1,028.89 kB, gzip은 302.13 → 324.96 kB였음. 관련 없는 정비·경고 숨김은 하지 않았음.
  • 이번 React 변경의 Linux·원격 CI·미리보기·Safari/Firefox 실제 실행·Chromatic dry-run/게시/시각 비교·Sentry 인증된 sourcemap 업로드는 PR 생성 전에 미실행임.

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

  • React·DOM·타입의 버전 정합성과 다른 직접 의존성·제한·override 보존을 확인함.
  • 최신 콜백을 읽는 Effect Event의 호출 위치와 enabled·throttleMs 재구독·마지막 실행 시각 정책이 유지되는지 확인함.
  • ref 타입 수정과 복사 상태 테스트의 비동기 대기가 실제 호환성 문제만 해결하는지 확인함.
  • PR 생성 후 Linux CI의 설치·전체 테스트·lint와 미리보기 결과를 확인함. 생성 전 결과를 통과로 기록하지 않음.

일반 PR CI가 수행하지 않는 엄격한 설치·doctor·앱/Storybook 빌드·브라우저 검증은 아래 명령과 동작으로 보완함.

npm ci --engine-strict --strict-peer-deps
npm ls --depth=0
npx --no-install vitest run
npm run lint
npm run build
npm run build-storybook -- --disable-telemetry
npx --no-install storybook doctor
npm run storybook
npx --no-install vite preview --outDir storybook-static --port 6007
npm run dev

Storybook 개발·정적 결과에서 UpdateModal 12개 play·긴 설명 스크롤·체크박스·닫기·양방향 args·Actions·Docs, 청중 모바일 실제 크기와 MSW 테이블·투표·청중 시뮬레이터를 확인함. 고정 viewport는 관리 화면의 미리보기 공간을 충분히 확보하고 iframe의 실제 크기를 확인함. 빌드 후 앱 서버를 재시작해 정상 진입과 의존성 탐색을 확인함.

실제 개발 서버의 사용자 검증은 아래와 같으며 사용자가 1~10 모두 정상 동작했다고 보고했음. 배포된 다른 프런트엔드의 성공과 혼동하지 않도록 이번 로컬 React 19 앱과 개발 API·소켓을 연결해 확인했음. 로그인한 사회자 A, 별도 청중 세션 B, 사회자 교체용 추가 탭 C로 검증했음.

번호 사용자 실행 시나리오 기대 결과와 보고
1 로그인·테스트 테이블 저장·재조회 이름·주제·팀명·순서·시간 유지, 통과함
2 A에서 라이브 공유·링크 복사 후 B 접속 같은 테이블·순서·시간 표시, 통과함
3 재생·정지·Space·다음/이전 순서 B 동기화와 중복 실행·차례 이동 없음, 통과함
4 자유토론의 발언 팀 전환·정지·재개 활성 팀·전체/발언 시간 동기화, 통과함
5 A와 B 각각 다른 탭 뒤에서 15초 대기 후 복귀 현재 시간 보정과 초기화·중복 실행 없음, 통과함
6 A와 B 각각 Network Offline 후 복구 연결·재구독 복구와 최신 상태 반영, 통과함
7 C에서 같은 테이블 공유 후 A에서 다시 공유 이전 사회자 교체 안내와 활성 사회자 전환, 통과함
8 마지막 순서 후 토론 종료 A·B 각각 종료 화면 이동, 통과함
9 투표 생성·이름/팀 선택·취소·재제출 빈 입력 차단·입력 보존·완료 화면·참여자 목록 반영, 통과함
10 투표 종료 후 결과 확인 참여자 수·팀별 결과가 제출 내용과 일치, 통과함

남은 확인 범위임.

  • Safari·Firefox의 주요 화면·타이머·모달·입력·스크롤과 전체 Tab 이동·닫기 후 포커스 복원을 확인함.
  • 최초 폰트 미적용이 재현되면 새로고침 전에 Pretendard CSS·woff2 요청 실패와 실제 사용 폰트를 확인함.
  • 미리보기의 /pr-<PR 번호> 경로에서 새로고침·영어 경로·번역·정적 자산 로딩을 확인함.
  • 인증된 환경에서 Sentry sourcemap 생성·업로드·업로드 후 삭제를 확인함. 현재 검증으로 실제 업로드 성공을 주장하지 않음.
  • 필요 시 Linux 엄격한 설치·doctor·Storybook 빌드와 npm run chromatic -- --dry-run --no-interactive --disable-telemetry를 실행함. 실제 Chromatic 게시·시각 비교는 별도 권한 범위에서 수행함.

Summary by CodeRabbit

  • 문서
    • 앱이 React 19를 사용한다고 안내하고, 최신 콜백 처리와 React·UI 컨벤션을 업데이트했습니다.
  • 개선 사항
    • 최신 표시 상태 콜백을 처리하는 방식을 React 19에 맞게 변경했습니다. 표시 감지 동작은 유지됩니다.
    • React Hooks 검사 기준을 구체화했습니다.
  • 테스트
    • 오류 경계, 타이머, 표시 상태 감지, 소켓 구독 및 포털의 Strict Mode 동작과 정리를 검증하는 테스트를 보강했습니다.
  • 기타
    • React 및 관련 타입, Hooks 검사 도구를 업데이트했습니다.

@i-meant-to-be i-meant-to-be added the config 외부 라이브러리 관련 추가 및 설정 label Oct 15, 2026
@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6c6a17e7-619e-4ee1-842e-519ef909b973
📥 Commits

Reviewing files that changed from the base of the PR and between a79745e and d39a9c0.

⛔ Files ignored due to path filters: 1
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing: 15
  • docs/README.md
  • docs/architecture/structure-and-ui.md
  • docs/conventions/react-and-ui.md
  • eslint.config.js
  • future.md
  • package.json
  • src/components/ErrorBoundary/ErrorBoundary.test.tsx
  • src/hooks/sockets/useSocket.test.ts
  • src/hooks/useDocumentVisibility.test.ts
  • src/hooks/useDocumentVisibility.ts
  • src/page/TimerPage/components/LiveShareModal.test.tsx
  • src/page/TimerPage/components/LiveShareModal.tsx
  • src/page/TimerPage/hooks/useNormalTimer.test.ts
  • src/page/TimerPage/hooks/useTimerPageState.ts
  • src/util/GlobalPortal/GlobalPortal.test.tsx
💤 Files with no reviewable changes: 1
  • future.md

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Walkthrough

React 18에서 React 19로 의존성을 갱신했습니다. useDocumentVisibility는 useEffectEvent로 최신 콜백을 호출합니다. Strict Mode에서 훅과 포털의 정리 동작을 확인하는 테스트를 추가하고, React 19에 필요한 타입 변경을 반영했습니다.

Changes

React 19 마이그레이션

Layer / File(s) Summary
React 19 의존성과 훅 규칙 갱신
package.json, eslint.config.js, docs/README.md, docs/architecture/structure-and-ui.md, docs/conventions/react-and-ui.md, future.md
React 및 관련 타입과 eslint-plugin-react-hooks 버전을 갱신했습니다. 훅 검사 규칙과 useEffectEvent 사용 기준을 변경하고, 문서의 React 버전을 갱신했습니다. future.md를 삭제했습니다.
표시 상태 훅의 최신 콜백 처리
src/hooks/useDocumentVisibility.ts, src/hooks/useDocumentVisibility.test.ts
훅이 useEffectEvent로 최신 콜백을 호출하도록 변경했습니다. 콜백 변경, 활성화 옵션, 제한 시간, Strict Mode의 리스너 정리를 테스트합니다.
Strict Mode 정리와 React 19 호환성 검증
src/components/ErrorBoundary/ErrorBoundary.test.tsx, src/hooks/sockets/useSocket.test.ts, src/page/TimerPage/hooks/useNormalTimer.test.ts, src/util/GlobalPortal/GlobalPortal.test.tsx, src/page/TimerPage/components/LiveShareModal.tsx, src/page/TimerPage/components/LiveShareModal.test.tsx, src/page/TimerPage/hooks/useTimerPageState.ts
오류 복구 및 Sentry 수집, 소켓 구독, 타이머, 포털의 동작과 정리를 검증하는 테스트를 추가했습니다. LiveShareModal의 타이머 ref 타입과 volumeRef의 null 허용 타입을 변경했습니다.

Priority: ➖ Normal

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

Change: Other

Suggested reviewers: coli-geonwoo

Merge Risk: ⚪ Minimal · up to d39a9

This PR upgrades React to 19 and simplifies the tab-visibility hook while preserving its public contract. No specific merge-blocking risk was identified in the supplied changes. Remote CI and cross-browser checks should still run as usual before release.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 10 files. (4 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed 제목은 React 19.3.1 마이그레이션과 useEffectEvent 도입이라는 PR의 주요 변경 사항을 정확하고 간결하게 설명합니다.
Linked Issues check Passed 직접 연결된 활성 이슈 #529의 코딩 요구사항을 충족함. package.json은 React·React DOM을 ^19.3.1, 타입 패키지를 ^19.3.0, eslint-plugin-react-hooks를 ^7.1.1로 갱신함. LiveShareModal의 timer ref와 TimerPageLogics.volumeRef의 Re…
Out of Scope Changes check Passed 변경 범위는 #529의 React 19 마이그레이션, useEffectEvent 도입, 호환성 수정, 회귀 테스트, 관련 문서와 검증 기록에 연결됨. ESLint 설정 변경은 이슈가 요구한 기존 Hooks 규칙 유지와 연결됨. future.md 삭제는 PR 설명상 임시 추적 문서 정리이며 검증 한계와 후속 기록을 PR에 보존함. 타이머·소켓·Port…
Full details: Docstring Coverage

Explanation

Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 10 files. (4 skipped: 4 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@github-actions

Copy link
Copy Markdown

🚀 Preview 배포 완료!

환경 URL
Preview 열기
API Dev 환경

PR이 닫히면 자동으로 정리됩니다.

@i-meant-to-be
i-meant-to-be merged commit e458940 into develop Oct 15, 2026
3 checks passed
@github-actions

Copy link
Copy Markdown

🧹 Preview 배포가 정리되었습니다.

This branch was successfully deployed

2 active deployments
PREVIEW_ENV — d39a9c0f Deployed Oct 15, 2026 by i-meant-to-be via cleanup #48
DEPLOY_DEV — d39a9c0f Deployed Oct 15, 2026 by i-meant-to-be via build-and-deploy #200
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

config 외부 라이브러리 관련 추가 및 설정

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[CONFIG] React 19.3.1 마이그레이션 및 useEffectEvent 도입

1 participant