Skip to content

fix: 상세 웹뷰 uri를 마운트 시점 구독 상태로 고정한다 - #38

Open
seongwon030 wants to merge 1 commit into
mixpanel-identity-split-auditfrom
fix/club-detail-uri-stable
Open

seongwon030 wants to merge 1 commit into
mixpanel-identity-split-auditfrom
fix/club-detail-uri-stable

Conversation

@seongwon030

@seongwon030 seongwon030 commented Sep 20, 2026 •

Copy link
Copy Markdown
Member

스택 PR입니다. base가 main이 아니라 #36의 브랜치입니다. 같은 useMemo를 건드려서 충돌을 피하려고 쌓았습니다. #36이 머지되면 GitHub이 base를 main으로 자동 전환합니다. 리뷰 diff는 이 PR의 커밋 1개뿐입니다.

문제

uri useMemo가 isSubscribed에 의존합니다. 구독을 토글하면:

  1. subscribed-clubs-context.tsx:147 setSubscribedClubIds (즉시)
  2. → isSubscribed 함수 아이덴티티 변경
  3. → uri 재계산, is_subscribed=true가 붙거나 빠짐
  4. → source.uri 변경 → 웹뷰가 그 URL을 다시 로드

리로드가 실제로 일어나는 근거

react-native-webview@13.15.0 소스에서 확인했습니다. 추정이 아닙니다.

iOS — apple/RNCWebViewImpl.m:794-802

- (void)setSource:(NSDictionary *)source {
  if (![_source isEqualToDictionary:source]) {   // uri가 다르면 불일치
    _source = [source copy];
    if (_webView != nil) [self visitSource];     // → NSURLRequest → loadRequest
  }
}

Fabric의 RNCWebView.mm:472-495가 prop 갱신마다 source 딕셔너리를 조건 없이 만들어 이 setter를 호출합니다.

Android — RNCWebViewManagerImpl.kt:401-405

val previousUrl = view.url          // 이전 prop이 아니라 "현재 표시 중인 URL"
if (previousUrl != null && previousUrl == url) return
...
view.loadUrl(url!!, headerMap)

상세는 /webview/club/:id로 진입해 /clubDetail/:id로 리다이렉트되고, 웹이 history.replaceState로 session_id까지 URL에서 지웁니다(initSDK.ts). 그래서 view.url과 prop의 uri가 같아질 수 없고, 가드가 무력해져 source prop이 바뀌면 항상 loadUrl입니다.

왜 지금은 안 터지나 — 그리고 #34와의 관계

이 화면에서 구독을 토글할 경로가 현재 없습니다.

  • 웹 종 버튼이 보내는 SUBSCRIBE_TOGGLE을 use-webview-message-handler가 몰라서 default에서 버립니다.
  • 네이티브 종 버튼(handleSubscribeToggle)은 {hasError && (<Header>…)} 안이라 에러 화면에서만 렌더링됩니다.

#34가 SUBSCRIBE_TOGGLE 처리를 추가하면 종을 누를 때마다 리로드가 납니다. 그리고 #34가 리로드와 경합합니다 — setSubscribedClubIds가 먼저 실행돼 로드가 시작되고, SUBSCRIBE_RESULT 주입은 스토리지 저장·FCM 토큰·서버 동기화를 모두 await한 뒤라서, 토스트가 갈아엎히는 문서에 꽂힙니다.

이 PR은 #34를 막지 않습니다. #34 머지 전에 들어가면 그 체크리스트 1번(종 클릭 → 토스트)이 리로드 없이 통과합니다.

해법

웹은 이 파라미터를 ClubDetailPage.tsx:206의 initialIsSubscribed로만 씁니다 — 첫 페인트에 종 아이콘이 잘못된 상태로 깜빡이는 걸 막는 용도입니다. 실시간 상태는 ClubDetailTopBar.tsx:75가 SUBSCRIBE_STATE 메시지로 받습니다.

그래서 마운트 시점 값으로 고정해도 기능이 줄지 않습니다. 이름부터 initialIsSubscribed입니다.

파라미터를 없애지는 않았습니다 — 없애면 첫 페인트가 깜빡입니다.

네이티브 헤더 종 아이콘용 subscribed useMemo는 그대로 라이브 상태를 씁니다. 거긴 리로드와 무관합니다.

확인

리뷰어가 실기기로 봐줄 것: 종 탭 시 페이지가 다시 뜨지 않고 스크롤 위치가 유지되는지.

범위 밖

sessionId가 나중에 채워지는 경우(부트스트랩 완료 전 딥링크 진입)에도 uri가 바뀌어 같은 리로드가 납니다. 그건 홈(home-webview-screen.tsx)처럼 세션 로딩 게이트를 두는 문제라 이 PR에 넣지 않았습니다.

uri useMemo가 isSubscribed에 의존하고 있어서, 구독을 토글하면
subscribed-clubs-context가 setSubscribedClubIds로 목록을 갈아끼우고 →
isSubscribed 함수 아이덴티티가 바뀌고 → uri가 재계산되어 is_subscribed=true가
붙거나 빠진다. 그러면 source.uri가 바뀌고 웹뷰가 그 URL을 다시 로드한다.

react-native-webview 13.15.0 기준:
- iOS: RNCWebViewImpl.m setSource가 source 딕셔너리를 비교해 다르면 visitSource
  -> loadRequest.
- Android: RNCWebViewManagerImpl.kt loadSource가 view.url(현재 표시 중인 URL)과
  비교하는데, 상세는 /webview/club/:id 로 진입해 /clubDetail/:id 로 리다이렉트되고
  웹이 history.replaceState로 session_id까지 지우므로 두 값이 같아질 수 없다.
  즉 가드가 무력해서 source prop이 바뀌면 항상 loadUrl.

지금은 이 화면에서 구독을 토글할 방법이 없어 드러나지 않는다. 웹 종 버튼이
보내는 SUBSCRIBE_TOGGLE을 use-webview-message-handler가 모르고,
네이티브 종 버튼은 hasError일 때만 렌더링된다. #34가 SUBSCRIBE_TOGGLE 처리를
추가하면 종을 누를 때마다 리로드가 발생한다.

웹은 이 파라미터를 ClubDetailPage의 initialIsSubscribed(첫 페인트 깜빡임 방지)
로만 쓰고 이후 상태는 SUBSCRIBE_STATE 메시지로 받으므로, 마운트 시점 값으로
고정해도 기능이 줄지 않는다. 파라미터를 없애면 첫 페인트가 깜빡여서 남긴다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 20, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 29 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: ba8d6d67-b51b-49a1-a0a4-cfebc6c15981

📥 Commits

Reviewing files that changed from the base of the PR and between d93b500 and 3e256f2.

📒 Files selected for processing (1)
  • ui/club-detail/club-detail-screen.tsx

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.

@seongwon030

Copy link
Copy Markdown
Member Author

검증 정정: 이 PR을 올릴 때 적은 tsc --noEmit 통과는 실제로는 검증되지 않은 상태였습니다. 워크트리에 node_modules가 없어 npx tsc가 typescript를 찾지 못했는데, 래퍼가 완료 메시지를 출력해서 통과로 잘못 읽었습니다.

의존성을 설치하고 이 브랜치에서 다시 돌렸습니다:

  • tsc --noEmit — 통과 (exit 0)
  • npm run lint — 0 errors, 5 warnings 모두 기존 import/no-named-as-default (변경과 무관)

결과적으로 원래 주장은 맞았지만, 그때는 근거가 없었습니다. 본문의 동작 검증(입출력 시뮬레이션)은 실제로 돌린 것이라 유효합니다.

🤖 Generated with Claude Code

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.

1 participant