Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Walkthrough알림 존재 여부 조회 API를 추가하고, 인증 준비 상태(Zustand store)를 도입하여 소셜 로그인 토큰 획득 흐름 완료 시 준비 완료 플래그를 설정합니다. 헤더는 준비 및 인증 상태를 감지해 알림 뱃지 아이콘을 조건부로 표시합니다. 팔로워/팔로잉 리스트는 총 개수 필드 수신과 프리페치/무한 스크롤 로직을 보강했습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as User
participant H as MainHeader
participant AR as useAuthReadyStore
participant SL as useSocialLoginToken
participant API as apiClient
participant S as Server
U->>SL: 앱 진입 / 소셜 로그인 콜백 처리
SL->>AR: setReady(true) (성공/실패/비적용 경로 포함)
Note right of AR: isReady = true
H->>AR: isReady 구독
H->>H: 로컬스토리지에서 authToken 확인
alt isReady && authToken 존재
H->>API: GET /notifications/exists-unchecked
API->>S: 요청
S-->>API: { exists: boolean }
API-->>H: 응답 데이터
H->>H: hasUnchecked 업데이트
H->>U: exist-bell.svg 또는 bell.svg 렌더
else
H->>U: 기본 bell.svg 렌더
end
sequenceDiagram
autonumber
actor U as User
participant P as FollowerListPage
participant API as users.get(Followers/Following)
participant S as Server
U->>P: 페이지 진입
P->>API: 첫 페이지 로드
API->>S: 요청
S-->>API: data, nextCursor, totalCount?
API-->>P: 응답
P->>P: 목록/TotalBar 렌더, 초기 스피너 해제
alt 콘텐츠 높이 < 뷰포트
P->>API: 다음 페이지 프리페치
API->>S: 요청
S-->>API: 응답
API-->>P: 데이터 추가
end
loop 스크롤 근접(100px)
P->>API: 다음 페이지 로드
API->>S: 요청
S-->>API: 응답
API-->>P: 목록 추가 / 바닥 스피너 토글
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/pages/feed/FollowerListPage.tsx (1)
32-36: 중복 요청 위험: loading의 최신값을 보지 못하는 stale closure 문제
- handleScroll에서
loading을 참조하지만 deps에 포함되지 않아 오래된 값으로 판단합니다.loadUserList도loading을 캡처하지만 deps에 없어서 최신값을 보지 못할 수 있습니다.- 결과적으로 스크롤 중 중복 fetch가 발생할 수 있습니다. 또한 스크롤 리스너는 passive로 등록하는 게 성능상 안전합니다.
아래 최소 수정으로 막을 수 있습니다.
- useEffect(() => { + useEffect(() => { const handleScroll = () => { // 로딩 중이거나 마지막 페이지이거나 에러가 있거나 재시도 횟수 초과시 요청하지 않음 if (loading || isLast || error || retryCount >= 3 || !nextCursor) { console.log('스크롤 요청 차단:', { loading, isLast, error, retryCount, nextCursor }); return; } @@ - window.addEventListener('scroll', handleScroll); + window.addEventListener('scroll', handleScroll, { passive: true }); return () => window.removeEventListener('scroll', handleScroll); - }, [isLast, error, retryCount, nextCursor, loadUserList]); + }, [isLast, error, retryCount, nextCursor, loading, loadUserList]);그리고
loadUserList도 최신loading을 보도록 deps를 보강하세요:- const loadUserList = useCallback( + const loadUserList = useCallback( async (cursor?: string) => { if (loading) return; @@ - [type, userId], + [type, userId, loading], );대안(권장): 중복 요청 방지를 확실히 하려면
useRef로 in-flight 플래그를 둬도 됩니다.// 상단 const isFetchingRef = useRef(false); // 내부 if (isFetchingRef.current) return; isFetchingRef.current = true; try { ... } finally { isFetchingRef.current = false; }Based on learnings (React 19의 useEffectEvent도 이벤트 핸들러 최신 상태 관찰에 유용).
Also applies to: 101-103, 104-125
♻️ Duplicate comments (1)
src/api/users/getFollowerList.ts (1)
12-12: follower 쪽도 동일하게 정렬되어 좋습니다. 명명 통합 어댑터 권장
- following과 대칭으로 잘 추가되었습니다.
- 위 파일 코멘트와 동일: 도메인 매핑으로 totalCount 통합, nextCursor null 가능성 계약 확인.
🧹 Nitpick comments (10)
src/stores/useAuthReadyStore.ts (1)
8-11: 잘 추가되었습니다. 작고 명확한 스토어입니다.
- v5의 named import 사용 OK.
- 선택자 상수화와 명명 명확화 제안:
- 예: isAuthReady로 명확히 하고, selector를 export 해서 재사용/리렌더 최소화.
예시:
- export const selectIsAuthReady = (s: AuthReadyState) => s.isReady
- export const selectSetAuthReady = (s: AuthReadyState) => s.setReady
Based on learnings
src/api/notifications/getNotificationExist.ts (1)
13-18: API 모듈 OK. 소비자에 boolean만 노출하는 래퍼 추가를 권장
- 현재 형태 유지하되, UI에서는 boolean을 바로 쓰도록 작은 헬퍼를 추가하면 사용성이 좋아집니다.
추가 함수 예시(동파일 내):
export const hasUncheckedNotifications = async (): Promise<boolean> => { const res = await getNotificationExist(); return res.isSuccess && !!res.data?.exists; };또한 React Query를 사용할 경우, enabled 플래그로 인증 준비 상태를 연동하면 호출 시점을 더 깔끔하게 제어할 수 있습니다(아래 MainHeader 코멘트 참고).
src/hooks/useSocialLoginToken.ts (1)
55-69: ready 처리 타이밍 안정화 및 소비 측 대안 제안
- setReady(true)를 모든 경로에서 호출한 점 좋습니다. 안전성을 위해 finally로 감싸면 조기 리턴/예외에도 확실합니다.
예시:
- } catch (error) { - console.error('💥 토큰 발급 중 오류 발생:', error); - } - // 토큰 발급 시도 완료 시점에 ready true - setReady(true); + } catch (error) { + console.error('💥 토큰 발급 중 오류 발생:', error); + } finally { + setReady(true); + }
- MainHeader가 전역 ready를 구독하는 대신, 이 훅이 노출하는 waitForToken을 사용해 소비 측에서 await하는 방법도 고려해볼 수 있습니다.
- 장점: 전역 상태 결합도↓, 호출 지점에서 의도를 명확히 표현.
- 단점: 여러 소비자가 있으면 각자 await 호출 필요.
src/components/common/MainHeader.tsx (2)
22-37: 토큰 발급 전 호출 이슈: enabled 기반의 데이터 패칭으로 단순화 권장현재 isAuthReady + localStorage 검사로 잘 가드하고 있습니다. 더 견고/단순하게 하려면:
- React Query 도입 시:
- enabled: isAuthReady && !!authToken
- refetchOnWindowFocus: true, staleTime으로 중복 요청 감소
- 에러/로딩 상태 표준화
간단 예시:
const authToken = typeof window !== 'undefined' ? localStorage.getItem('authToken') : null; const { data } = useQuery({ queryKey: ['notifications', 'exists'], queryFn: getNotificationExist, enabled: isAuthReady && !!authToken, staleTime: 60_000, refetchOnWindowFocus: true, }); useEffect(() => { if (data?.isSuccess) setHasUnchecked(data.data.exists); }, [data]);
- 대안: axios 인터셉터 + TokenManager로 “토큰 준비 Promise”를 모든 요청이 자동 대기하도록 구성하면 컴포넌트마다 가드 로직이 불필요해집니다.
장단:
- React Query: 구현 난이도 낮고 컴포넌트 단위로 제어 용이.
- 인터셉터: 전역 일관성↑, 초기 설정 난이도↑.
48-52: 접근성 소소 개선 제안
- alt를 상태 반영형으로 분기하면 보조기기 사용성이 좋아집니다.
- 예: alt={hasUnchecked ? '읽지 않은 알림 있음' : '알림 없음'}
src/pages/feed/FollowerListPage.tsx (5)
82-90: totalCount 설정 로직: 첫 페이지/후속 페이지 분리 + 폴백 추가 권장API에 total 값이 없으면 “전체 0”이 노출될 수 있습니다. 첫 페이지에서는 API total을 사용하되, 없으면 길이로 폴백하고, 이후 페이지는 누적으로 합산하는 편이 견고합니다.
- // 총합 카운트 설정 (API별 키 분기) - if (type === 'followerlist') { - const total = (response.data as { totalFollowerCount?: number }).totalFollowerCount; - if (typeof total === 'number') setTotalCount(total); - } else { - const total = (response.data as { totalFollowingCount?: number }).totalFollowingCount; - if (typeof total === 'number') setTotalCount(total); - } - // setTotalCount(prev => prev + userData.length); + // 총합 카운트 설정: 첫 페이지는 API total, 없으면 길이로 폴백 / 이후 페이지는 누적 + if (!cursor) { + const total = type === 'followerlist' + ? (response.data as { totalFollowerCount?: number }).totalFollowerCount + : (response.data as { totalFollowingCount?: number }).totalFollowingCount; + setTotalCount(typeof total === 'number' ? total : userData.length); + } else { + setTotalCount(prev => prev + userData.length); + }
104-125: 무한스크롤: scroll 계산 대신 IntersectionObserver로 단순·정확하게현재 스크롤 계산 + 프리페치 이펙트 두 경로를 합쳐, sentinel을 관찰하는 IntersectionObserver 한 경로로 단순화하면 정확도와 성능이 좋아집니다(리플로우/리스너 부담 감소).
예시:
// import { useRef } from 'react' const sentinelRef = useRef<HTMLDivElement>(null); useEffect(() => { if (isLast || error || retryCount >= 3) return; const el = sentinelRef.current; if (!el) return; const io = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting && !loading && nextCursor) { loadUserList(nextCursor); } }, { root: null, rootMargin: '100px' } // 기존 threshold 유지 ); io.observe(el); return () => io.disconnect(); }, [isLast, error, retryCount, nextCursor, loading, loadUserList]); // 렌더 하단 <div ref={sentinelRef} style={{ height: 1 }} />Based on learnings
Also applies to: 131-139
143-169: 로딩 스피너 접근성 보완보조기기에 로딩 상태를 알려주도록 live region/role을 추가하세요.
- {loading && userList.length === 0 ? ( - <LoadingSpinner size="medium" fullHeight={true} /> + {loading && userList.length === 0 ? ( + <div aria-live="polite" role="status"> + <LoadingSpinner size="medium" fullHeight={true} /> + </div> ) : ( @@ - {loading && userList.length > 0 && ( - <div style={{ display: 'flex', justifyContent: 'center', padding: '16px 0' }}> - <LoadingSpinner size="small" /> - </div> - )} + {loading && userList.length > 0 && ( + <div aria-live="polite" role="status" style={{ display: 'flex', justifyContent: 'center', padding: '16px 0' }}> + <LoadingSpinner size="small" /> + </div> + )}
180-180: 중첩된 100vh로 여백 과다/스크롤 이슈 가능Wrapper와 List 모두
min-height: 100vh라 중첩되어 여백이 과해질 수 있습니다. List는 header 높이를 뺀 값으로 계산하는 게 안전합니다.- min-height: 100vh; + min-height: calc(100vh - 105px);Also applies to: 204-207
20-20: PR 요구사항 1 답변: 알림 API 호출 시 토큰 준비 문제 — enabled/selector 기반 접근 추천MainHeader에서 전역 토큰/준비 플래그를 폴링하기보다, 데이터 패칭 레이어에서 토큰 준비를 제어하세요.
- React Query 권장 패턴:
- fetcher는 항상 현재 토큰을 읽고(예: zustand selector),
- useQuery에
enabled: isAuthReady && !!accessToken로 조건부 활성화를 거세요.- 필요 시 axios 인스턴스에 요청 인터셉터로 토큰 주입(또는 준비 전 대기) 구현.
- 토큰 변경에 따른 재호출은 query key에 토큰 또는 userId를 일부로 포함해 자연스럽게 재검증.
예시:
const accessToken = useAuthStore(s => s.token) const isAuthReady = useAuthStore(s => s.isReady) useQuery({ queryKey: ['notification-exists', { userId, token: !!accessToken }], queryFn: () => api.get('/notifications/exists'), enabled: isAuthReady && !!accessToken, staleTime: 60_000, refetchOnMount: false, })이 방식이면 “토큰 발급 이전 호출” 자체가 발생하지 않습니다. 현재 구현과 비교해볼지 확인 부탁드립니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (2)
src/assets/header/bell.svgis excluded by!**/*.svgsrc/assets/header/exist-bell.svgis excluded by!**/*.svg
📒 Files selected for processing (7)
src/api/notifications/getNotificationExist.ts(1 hunks)src/api/users/getFollowerList.ts(1 hunks)src/api/users/getFollowingList.ts(1 hunks)src/components/common/MainHeader.tsx(3 hunks)src/hooks/useSocialLoginToken.ts(2 hunks)src/pages/feed/FollowerListPage.tsx(7 hunks)src/stores/useAuthReadyStore.ts(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
src/api/notifications/getNotificationExist.ts (1)
src/api/index.ts (1)
apiClient(7-13)
src/pages/feed/FollowerListPage.tsx (1)
src/types/user.ts (1)
UserProfileType(1-1)
src/hooks/useSocialLoginToken.ts (1)
src/stores/useAuthReadyStore.ts (1)
useAuthReadyStore(8-11)
src/components/common/MainHeader.tsx (3)
src/stores/useAuthReadyStore.ts (1)
useAuthReadyStore(8-11)src/api/notifications/getNotificationExist.ts (1)
getNotificationExist(13-18)src/components/common/IconButton.tsx (1)
IconButton(3-7)
🔇 Additional comments (1)
src/api/users/getFollowingList.ts (1)
12-12: totalFollowingCount 추가 — 타입/명명 합의 필요
- follower/following 공통 처리를 위해 도메인 계층에서 totalCount로 매핑하는 어댑터 도입 권장(조건 분기 감소)
- nextCursor가 빈 문자열("") 또는 null로 내려올 가능성을 백엔드 계약에서 확인하고, 필요 시 응답 타입을
string | null로 조정하세요
#️⃣연관된 이슈
없음
📝작업 내용
스크린샷
💬리뷰 요구사항
Summary by CodeRabbit