Conversation
- 새 탭(window.open) 대신 같은 화면에서 navigate()로 이동하도록 변경
- PostBody: 게시물 본문 클릭 시 window.open → navigate('/feed/:feedId')
- PostFooter: 댓글 아이콘 클릭 시 window.open → navigate('/feed/:feedId')
- FeedDetailPage: 뒤로가기 핸들러 정리
- window.close() / window.opener 체크 제거
- navigate(-1) 단일 호출로 단순화
- Feed: 피드 목록 상태 및 스크롤 위치를 sessionStorage에 캐싱하여 복원
- 게시물 상세에서 돌아올 때 API 재호출 없이 캐시 데이터로 즉시 렌더링
- 10분 TTL 초과 시 자동으로 캐시 무효화
- 스크롤 이벤트(100ms 디바운스)로 scrollY를 sessionStorage에 주기 저장
- 마운트 시 저장된 scrollY로 스크롤 위치 복원(rAF 2회)
- 탭 전환 시 scrollTo(0, 0) 정상 동작 유지
- navigate(state: { initialTab }) 진입 시 캐시 무시하고 새로 로드
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. ℹ️ Recent review infoConfiguration used: Repository UI Review profile: CHILL Plan: Pro Disabled knowledge base sources:
📒 Files selected for processing (5)
WalkthroughPost 컴포넌트 클릭이 클라이언트 라우팅으로 변경되었고, 피드 페이지에 sessionStorage 기반 캐시 훅 및 초기 복원과 무한 스크롤 초기화 로직이 추가되었으며, 여러 페이지가 공통 Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Post as PostComponent
participant Router
participant FeedDetail as FeedDetailPage
User->>Post: 게시물 클릭
Post->>Router: navigate(`/feed/${feedId}`)
Router->>FeedDetail: 라우팅 요청
FeedDetail-->>User: Feed 상세 뷰 렌더링
sequenceDiagram
participant Feed as FeedPage
participant Hook as useFeedCache
participant Session as sessionStorage
participant API as BackendAPI
Feed->>Hook: 마운트 시 초기 캐시 요청
Hook->>Session: readCache()
alt 캐시 존재 (TTL 유효)
Hook-->>Feed: initialCache 반환 (items, cursors, scrollY)
Feed->>Session: writeFeedCache(...) on tab/change
Feed->>API: loadMoreFeeds 이후 필요 시 추가 페치
Hook->>Feed: restore scroll (requestAnimationFrame x2)
else 캐시 없음
Hook-->>Feed: initialCache = null
Feed->>API: 초기 데이터 페치
Feed->>Session: writeFeedCache(...) on tab/change
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
src/pages/feed/Feed.tsx (3)
33-43: 만료된 캐시가 sessionStorage에서 제거되지 않습니다.
getInitialFeedCache에서 TTL이 만료된 경우null을 반환하지만, 만료된 항목을 sessionStorage에서 삭제하지 않습니다. 큰 문제는 아니지만, 정리하면 storage 공간을 확보할 수 있습니다.♻️ 만료 시 캐시 제거 제안
function getInitialFeedCache(): FeedCache | null { try { const raw = sessionStorage.getItem(FEED_CACHE_KEY); if (!raw) return null; const cache = JSON.parse(raw) as FeedCache; - if (Date.now() - cache.timestamp < FEED_CACHE_TTL) return cache; + if (Date.now() - cache.timestamp < FEED_CACHE_TTL) return cache; + sessionStorage.removeItem(FEED_CACHE_KEY); } catch { // ignore } return null; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/feed/Feed.tsx` around lines 33 - 43, getInitialFeedCache currently returns null for expired caches but leaves the stale entry in sessionStorage; update the function (getInitialFeedCache) to remove the expired item by calling sessionStorage.removeItem(FEED_CACHE_KEY) when the TTL check fails (i.e., when Date.now() - cache.timestamp >= FEED_CACHE_TTL) so the stale data is cleaned up before returning null; keep the existing try/catch behavior for parse errors.
148-158:loadMoreFeeds의 의존성 배열에loadTotalFeeds와loadMyFeeds가 누락되어 있습니다.두 함수는 현재 빈 의존성 배열로 안정적이지만, 향후 변경 시 stale closure 문제가 발생할 수 있고
react-hooks/exhaustive-deps경고가 발생할 수 있습니다.♻️ 의존성 배열 보완
- }, [activeTab, totalIsLast, totalLoading, totalNextCursor, myIsLast, myLoading, myNextCursor]); + }, [activeTab, totalIsLast, totalLoading, totalNextCursor, myIsLast, myLoading, myNextCursor, loadTotalFeeds, loadMyFeeds]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/feed/Feed.tsx` around lines 148 - 158, The useCallback for loadMoreFeeds is missing loadTotalFeeds and loadMyFeeds in its dependency array which can cause stale closures and lint warnings; update the dependency array of the useCallback that defines loadMoreFeeds to include loadTotalFeeds and loadMyFeeds, or if those functions are unstable, memoize them (e.g., wrap loadTotalFeeds/loadMyFeeds with useCallback where they are declared) so loadMoreFeeds remains correct and lint-clean.
239-239: sessionStorage.setItem에서 QuotaExceededError가 발생할 수 있습니다.피드 목록이 많거나
contentUrls에 긴 URL이 포함된 경우 직렬화된 데이터가 sessionStorage 용량(~5MB)을 초과할 수 있습니다.setItem호출을 try-catch로 감싸거나, 캐시할 포스트 수에 상한을 두는 것을 고려해 주세요. Line 212의 scroll persistence 쪽도 동일합니다.🛡️ setItem에 try-catch 추가 제안
- sessionStorage.setItem(FEED_CACHE_KEY, JSON.stringify(cache)); + try { + sessionStorage.setItem(FEED_CACHE_KEY, JSON.stringify(cache)); + } catch { + // QuotaExceededError — 캐시 저장 실패 무시 + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/feed/Feed.tsx` at line 239, Wrap the sessionStorage.setItem(FEED_CACHE_KEY, JSON.stringify(cache)) call in a try-catch and handle QuotaExceededError by either pruning the cache (limit number of posts or strip/shorten contentUrls) before retrying or by falling back to no-op (skip caching) and logging the failure; apply the same try-catch + graceful fallback to the scroll persistence sessionStorage.setItem usage as well so storage quota failures do not crash the page.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/common/Post/PostFooter.tsx`:
- Line 36: Import the missing hook from react-router-dom and add it to the
top-level imports so useNavigate() is defined; specifically, update the imports
in PostFooter.tsx to include useNavigate (used where const navigate =
useNavigate()) alongside the other React/third-party imports so the
ReferenceError is resolved.
In `@src/pages/feed/Feed.tsx`:
- Around line 226-250: The current guard in the useEffect uses
totalFeedPosts.length === 0 which prevents caching when only myFeedPosts are
loaded; update the condition to check the currently activeTab (activeTab) and
only bail out when the relevant list for that tab is empty (e.g., if activeTab
=== '전체' require totalFeedPosts.length > 0, if activeTab === '내 피드' require
myFeedPosts.length > 0), keeping other guards (initialLoading, tabLoading)
intact so sessionStorage.setItem(FEED_CACHE_KEY, ...) runs when the active tab's
data is present.
---
Nitpick comments:
In `@src/pages/feed/Feed.tsx`:
- Around line 33-43: getInitialFeedCache currently returns null for expired
caches but leaves the stale entry in sessionStorage; update the function
(getInitialFeedCache) to remove the expired item by calling
sessionStorage.removeItem(FEED_CACHE_KEY) when the TTL check fails (i.e., when
Date.now() - cache.timestamp >= FEED_CACHE_TTL) so the stale data is cleaned up
before returning null; keep the existing try/catch behavior for parse errors.
- Around line 148-158: The useCallback for loadMoreFeeds is missing
loadTotalFeeds and loadMyFeeds in its dependency array which can cause stale
closures and lint warnings; update the dependency array of the useCallback that
defines loadMoreFeeds to include loadTotalFeeds and loadMyFeeds, or if those
functions are unstable, memoize them (e.g., wrap loadTotalFeeds/loadMyFeeds with
useCallback where they are declared) so loadMoreFeeds remains correct and
lint-clean.
- Line 239: Wrap the sessionStorage.setItem(FEED_CACHE_KEY,
JSON.stringify(cache)) call in a try-catch and handle QuotaExceededError by
either pruning the cache (limit number of posts or strip/shorten contentUrls)
before retrying or by falling back to no-op (skip caching) and logging the
failure; apply the same try-catch + graceful fallback to the scroll persistence
sessionStorage.setItem usage as well so storage quota failures do not crash the
page.
ℹ️ Review info
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
src/components/common/Post/PostBody.tsxsrc/components/common/Post/PostFooter.tsxsrc/pages/feed/Feed.tsxsrc/pages/feed/FeedDetailPage.tsx
로딩 중 return <></>로 아무것도 렌더하지 않아 부모 Layout의 흰 배경이 노출되면서 깜빡임이 발생하던 문제 수정 - loading 상태: <Wrapper> + LoadingSpinner를 렌더해 어두운 배경 유지 - error / !feedData 상태: 세 개로 분리된 return <></> 를 <Wrapper /> 하나로 통합
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (6)
src/pages/feed/FeedDetailPage.tsx (3)
174-180:⚠️ Potential issue | 🟠 Major에러 분기가 중복되어 Wrapper 유지 로직이 무력화됩니다.
Line 174에서 이미
error를 반환해 Line 178의error || !feedData분기 중error경로는 도달하지 않습니다. 에러/빈 데이터 분기는 하나로 합쳐 주세요.🔧 제안 수정
- if (error) { - return <></>; - } - if (error || !feedData) { return <Wrapper />; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/feed/FeedDetailPage.tsx` around lines 174 - 180, Remove the duplicated error branch in FeedDetailPage.tsx: eliminate the early "if (error) return <></>;" and keep a single check "if (error || !feedData) return <Wrapper />;" so that both error and missing feedData are handled in one place; reference the variables error and feedData and the Wrapper return to locate and update the logic.
148-171:⚠️ Potential issue | 🔴 Critical로딩 분기의 return 문 이후 도달 불가능한 JSX가 남아있어 문법 오류가 발생합니다.
Line 153에서 return 문이 끝났는데 Line 154부터
</Wrapper>태그까지 도달 불가능한 JSX 코드가 남아있습니다. 머지 잔재로 보이며, 이 블록은 제거해야 합니다.🔧 제안 수정
if (loading) { return ( <Wrapper> <LoadingSpinner size="large" fullHeight={true} /> </Wrapper> ); - <TitleHeader - leftIcon={<img src={leftArrow} alt="뒤로가기" />} - onLeftClick={handleBackClick} - /> - <SkeletonWrapper> - <FeedPostSkeleton /> - {Array.from({ length: 5 }).map((_, index) => ( - <CommentSkeletonItem key={index}> - <Skeleton.Circle width={36} /> - <div style={{ flex: 1 }}> - <Skeleton.Text width={80} height={14} /> - <Skeleton.Text lines={2} height={14} gap={6} /> - </div> - </CommentSkeletonItem> - ))} - </SkeletonWrapper> - </Wrapper> - ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/feed/FeedDetailPage.tsx` around lines 148 - 171, The loading branch currently returns early (if (loading) return ...) but leftover unreachable JSX (the TitleHeader and subsequent SkeletonWrapper/FeedPostSkeleton block including TitleHeader, handleBackClick, SkeletonWrapper, CommentSkeletonItem) remains after that return causing a syntax error; remove the extraneous JSX that follows the early return so the if (loading) { return ( <Wrapper> <LoadingSpinner .../> </Wrapper> ); } block is the entire loading branch and ensure TitleHeader and SkeletonWrapper are only present outside this removed unreachable fragment where intended.
48-53:⚠️ Potential issue | 🔴 Critical중복된 const 선언과 import 누락으로 컴파일 오류가 발생합니다.
라인 48과 라인 50에서 동일한 스코프에
feedResponse를 재선언하고 있으며,getComments는 import되지 않아 컴파일이 실패합니다. 또한getFeedDetail이 중복으로 호출되고commentsResponse는 사용되지 않습니다.라인 48의
const feedResponse = await getFeedDetail(...)선언을 제거하고,getComments를@/api/comments/getComments에서 import해야 합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/feed/FeedDetailPage.tsx` around lines 48 - 53, Remove the redundant single-await declaration "const feedResponse = await getFeedDetail(...)" and keep a single Promise.all that fetches getFeedDetail and getComments; ensure getFeedDetail is only called once (inside the Promise.all) and either use or remove the returned commentsResponse accordingly. Add the missing import for getComments from "@/api/comments/getComments" and remove any unused variables (e.g., commentsResponse) if you decide not to consume comments in FeedDetailPage.src/pages/feed/Feed.tsx (3)
1-17:⚠️ Potential issue | 🔴 Criticalimport 정합성 오류로 빌드가 깨질 수 있습니다.
Line 14와 16에서
Container가 중복 선언되고, Line 259와 296에서 사용하는LoadingSpinnerimport가 누락되었습니다.🔧 제안 수정
import { FeedPostSkeleton, OtherFeedSkeleton } from '@/shared/ui/Skeleton'; +import LoadingSpinner from '@/components/common/LoadingSpinner'; import writefab from '../../assets/common/writefab.svg'; import { useNavigate, useLocation } from 'react-router-dom'; import { getTotalFeeds } from '@/api/feeds/getTotalFeed'; import { getMyFeeds } from '@/api/feeds/getMyFeed'; import { useSocialLoginToken } from '@/hooks/useSocialLoginToken'; import { useFeedCache, writeFeedCache } from '@/hooks/useFeedCache'; -import { Container } from './Feed.styled'; import { useInifinieScroll } from '@/hooks/useInifinieScroll'; import { Container, SkeletonWrapper } from './Feed.styled';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/feed/Feed.tsx` around lines 1 - 17, The file has an import conflict and a missing symbol: remove the duplicate Container import (both imports from './Feed.styled' — keep a single Container import) so there is only one Container symbol, and add an import for the LoadingSpinner component (used by this module with FeedPostSkeleton and OtherFeedSkeleton) so references to LoadingSpinner at runtime resolve; update the import block to import Container once and include LoadingSpinner alongside FeedPostSkeleton/OtherFeedSkeleton (and verify the LoadingSpinner export name matches the symbol used).
227-248:⚠️ Potential issue | 🔴 CriticaluseEffect 시작 블록이 누락되어 Hook 구조가 깨져 있습니다.
Line 227-248의 코드는
useEffect본문으로 작성되었으나, 대응하는useEffect(() => {시작이 없어 문법 오류입니다. Line 248의}, [activeTab, waitForToken, loadTotalFeeds, loadMyFeeds]);는 닫혀야 할useEffect블록이 없습니다.🔧 제안 수정(useEffect 래핑 추가)
const isLoading = initialLoading || tabLoading; + useEffect(() => { const loadFeedsWithToken = async () => { await waitForToken(); setTabLoading(true); try { const minLoadingTime = new Promise(resolve => setTimeout(resolve, 500)); if (activeTab === '피드') { await Promise.all([loadTotalFeeds(), minLoadingTime]); } else if (activeTab === '내 피드') { await Promise.all([loadMyFeeds(), minLoadingTime]); } } finally { setTabLoading(false); setInitialLoading(false); } }; loadFeedsWithToken(); }, [activeTab, waitForToken, loadTotalFeeds, loadMyFeeds]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/feed/Feed.tsx` around lines 227 - 248, Wrap the block that defines isLoading, loadFeedsWithToken and calls loadFeedsWithToken inside a useEffect so the Hook structure is valid: add a useEffect(() => { ... }, [activeTab, waitForToken, loadTotalFeeds, loadMyFeeds]) wrapper around the existing code (keeping isLoading, setTabLoading, setInitialLoading, loadFeedsWithToken, and the call to loadFeedsWithToken unchanged) so the effect runs when activeTab/waitForToken/loadTotalFeeds/loadMyFeeds change and the cleanup/try/finally behavior remains intact.
258-298:⚠️ Potential issue | 🔴 Critical렌더 분기 머지 충돌로 JSX 구조가 깨져 있습니다.
라인 258에서 시작한 삼항 연산자(
isLoading ?)가 라인 263에서 닫혀있지 않으며, 라인 264에서 또 다른 삼항 연산자(initialLoading || tabLoading ?)가 시작됩니다. 이로 인해 중괄호 불일치로 JSX 파싱이 실패합니다.구 렌더 로직(라인 258-263,
totalFeedPosts/myFeedPosts기반)과 신 렌더 로직(라인 264-298,totalFeed/myFeed기반)이 중첩되어 있습니다. 신 로직이 무한 스크롤, 스켈레톤 로딩, 센티널 처리를 포함하는 더 완전한 구현이므로, 구 로직을 제거하여 신 로직만 유지해야 합니다.제안 수정
<TabBar tabs={tabs} activeTab={activeTab} onTabClick={setActiveTab} /> - {isLoading ? ( - <LoadingSpinner size="large" fullHeight={true} /> - ) : activeTab === '피드' ? ( - <TotalFeed showHeader={true} posts={totalFeedPosts} isMyFeed={false} isLast={totalIsLast} /> - ) : ( - <MyFeed showHeader={false} posts={myFeedPosts} isMyFeed={true} isLast={myIsLast} /> {initialLoading || tabLoading ? ( activeTab === '내 피드' ? ( <OtherFeedSkeleton showFollowButton={false} paddingTop={136} /> ) : ( <SkeletonWrapper> {Array.from({ length: 3 }).map((_, index) => ( <FeedPostSkeleton key={index} /> ))} </SkeletonWrapper> ) ) : ( <> {activeTab === '피드' ? ( <> <TotalFeed showHeader={true} posts={totalFeed.items} isMyFeed={false} isLast={totalFeed.isLast} /> </> ) : ( <> <MyFeed showHeader={false} posts={myFeed.items} isMyFeed={true} isLast={myFeed.isLast} /> </> )} {!currentFeed.isLast && <div ref={currentFeed.sentinelRef} style={{ height: 40 }} />} {currentFeed.isLoadingMore && <LoadingSpinner size="small" fullHeight={false} />} </> )}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/feed/Feed.tsx` around lines 258 - 298, Remove the old, duplicated JSX branch that uses totalFeedPosts/myFeedPosts and leaves the isLoading ternary unclosed; keep only the newer render flow that handles initialLoading, tabLoading, infinite scroll and skeletons (the branch using totalFeed/myFeed, currentFeed.sentinelRef, and currentFeed.isLoadingMore). Specifically, update the JSX around isLoading, initialLoading and tabLoading so the top-level isLoading ? ... : ... ternary wraps the single new branch (which renders TotalFeed and MyFeed via totalFeed.items/myFeed.items and uses currentFeed for sentinel/loading), and delete the outdated TotalFeed(showHeader, posts={totalFeedPosts})/MyFeed(posts={myFeedPosts}) fragments to restore proper brace/parenthesis balance.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/hooks/useFeedCache.ts`:
- Around line 33-40: The writeFeedCache function currently calls
sessionStorage.setItem which can throw and break the feed render flow; wrap the
sessionStorage.setItem call (and the JSON.stringify step) in a try/catch inside
writeFeedCache to absorb any exceptions and avoid throwing to the UI, and in the
catch block log a non-fatal warning (e.g., console.warn or similar logger)
including the error and a short context message so failures are visible but do
not interrupt rendering.
---
Outside diff comments:
In `@src/pages/feed/Feed.tsx`:
- Around line 1-17: The file has an import conflict and a missing symbol: remove
the duplicate Container import (both imports from './Feed.styled' — keep a
single Container import) so there is only one Container symbol, and add an
import for the LoadingSpinner component (used by this module with
FeedPostSkeleton and OtherFeedSkeleton) so references to LoadingSpinner at
runtime resolve; update the import block to import Container once and include
LoadingSpinner alongside FeedPostSkeleton/OtherFeedSkeleton (and verify the
LoadingSpinner export name matches the symbol used).
- Around line 227-248: Wrap the block that defines isLoading, loadFeedsWithToken
and calls loadFeedsWithToken inside a useEffect so the Hook structure is valid:
add a useEffect(() => { ... }, [activeTab, waitForToken, loadTotalFeeds,
loadMyFeeds]) wrapper around the existing code (keeping isLoading,
setTabLoading, setInitialLoading, loadFeedsWithToken, and the call to
loadFeedsWithToken unchanged) so the effect runs when
activeTab/waitForToken/loadTotalFeeds/loadMyFeeds change and the
cleanup/try/finally behavior remains intact.
- Around line 258-298: Remove the old, duplicated JSX branch that uses
totalFeedPosts/myFeedPosts and leaves the isLoading ternary unclosed; keep only
the newer render flow that handles initialLoading, tabLoading, infinite scroll
and skeletons (the branch using totalFeed/myFeed, currentFeed.sentinelRef, and
currentFeed.isLoadingMore). Specifically, update the JSX around isLoading,
initialLoading and tabLoading so the top-level isLoading ? ... : ... ternary
wraps the single new branch (which renders TotalFeed and MyFeed via
totalFeed.items/myFeed.items and uses currentFeed for sentinel/loading), and
delete the outdated TotalFeed(showHeader,
posts={totalFeedPosts})/MyFeed(posts={myFeedPosts}) fragments to restore proper
brace/parenthesis balance.
In `@src/pages/feed/FeedDetailPage.tsx`:
- Around line 174-180: Remove the duplicated error branch in FeedDetailPage.tsx:
eliminate the early "if (error) return <></>;" and keep a single check "if
(error || !feedData) return <Wrapper />;" so that both error and missing
feedData are handled in one place; reference the variables error and feedData
and the Wrapper return to locate and update the logic.
- Around line 148-171: The loading branch currently returns early (if (loading)
return ...) but leftover unreachable JSX (the TitleHeader and subsequent
SkeletonWrapper/FeedPostSkeleton block including TitleHeader, handleBackClick,
SkeletonWrapper, CommentSkeletonItem) remains after that return causing a syntax
error; remove the extraneous JSX that follows the early return so the if
(loading) { return ( <Wrapper> <LoadingSpinner .../> </Wrapper> ); } block is
the entire loading branch and ensure TitleHeader and SkeletonWrapper are only
present outside this removed unreachable fragment where intended.
- Around line 48-53: Remove the redundant single-await declaration "const
feedResponse = await getFeedDetail(...)" and keep a single Promise.all that
fetches getFeedDetail and getComments; ensure getFeedDetail is only called once
(inside the Promise.all) and either use or remove the returned commentsResponse
accordingly. Add the missing import for getComments from
"@/api/comments/getComments" and remove any unused variables (e.g.,
commentsResponse) if you decide not to consume comments in FeedDetailPage.
ℹ️ Review info
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
src/components/common/Post/PostFooter.tsxsrc/hooks/useFeedCache.tssrc/pages/feed/Feed.tsxsrc/pages/feed/FeedDetailPage.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/common/Post/PostFooter.tsx
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/pages/feed/FeedDetailPage.tsx (2)
36-62:⚠️ Potential issue | 🟠 Major비동기 응답 경합으로 상세 데이터가 뒤바뀔 수 있습니다.
Line 37-58은 취소/최신성 보장이 없어, 이전 요청이 늦게 도착하면 현재
feedId의 상태를 덮어쓸 수 있습니다. 언마운트/파라미터 변경 시 무시 플래그(또는 AbortController 연계)로 최신 요청만 반영해 주세요.🔧 제안 수정
useEffect(() => { + let isActive = true; const loadFeedDetail = async () => { if (!feedId) { - setError('피드 ID가 없습니다.'); - setLoading(false); + if (!isActive) return; + setError('피드 ID가 없습니다.'); + setLoading(false); return; } try { - setLoading(true); + if (isActive) setLoading(true); const minLoadingTime = new Promise(resolve => setTimeout(resolve, 500)); const feedResponse = await getFeedDetail(Number(feedId)); await minLoadingTime; + if (!isActive) return; setFeedData(feedResponse.data); setError(null); } catch (err) { + if (!isActive) return; console.error('피드 상세 정보 로드 실패:', err); setError('피드 정보를 불러오는데 실패했습니다.'); } finally { - setLoading(false); + if (isActive) setLoading(false); } }; loadFeedDetail(); + return () => { + isActive = false; + }; }, [feedId]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/feed/FeedDetailPage.tsx` around lines 36 - 62, The loadFeedDetail effect can apply stale responses to state when feedId changes; modify the useEffect and its inner loadFeedDetail to cancel or ignore previous requests by creating an AbortController (or a local "isCurrent" flag) scoped to the effect, pass its signal into getFeedDetail (or check the flag before calling setFeedData/setError), and in the cleanup return set the controller.abort() (or flip the flag) so only the latest request updates state (update references in useEffect, loadFeedDetail, getFeedDetail call site, and the setFeedData/setError/setLoading usages).
38-49:⚠️ Potential issue | 🟡 Minor
feedId숫자 검증이 없어/feeds/NaN호출이 가능합니다.Line 48 전에
Number.isFinite검증을 추가해 잘못된 경로 입력을 조기에 차단해 주세요.🔧 제안 수정
if (!feedId) { setError('피드 ID가 없습니다.'); setLoading(false); return; } + const parsedFeedId = Number(feedId); + if (!Number.isFinite(parsedFeedId)) { + setError('유효하지 않은 피드 ID입니다.'); + setLoading(false); + return; + } try { setLoading(true); const minLoadingTime = new Promise(resolve => setTimeout(resolve, 500)); - const feedResponse = await getFeedDetail(Number(feedId)); + const feedResponse = await getFeedDetail(parsedFeedId);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/feed/FeedDetailPage.tsx` around lines 38 - 49, The current code only checks for a falsy feedId string and can call getFeedDetail(Number(feedId)) with NaN; before calling getFeedDetail, parse the id to a number and add a Number.isFinite check (e.g. const idNum = Number(feedId); if (!Number.isFinite(idNum)) { setError('유효하지 않은 피드 ID입니다.'); setLoading(false); return; }) to early-return on invalid paths; update subsequent calls to use idNum when calling getFeedDetail and keep existing setError/setLoading behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/pages/feed/Feed.tsx`:
- Around line 25-33: useFeedCache() eagerly reads and runs its restore effect
causing scroll restoration even when initialTabFromState is present; change
useFeedCache to accept an options param like { enabled?: boolean } and only call
readCache()/run the restore/save effects when enabled is true, and then update
the Feed component to call useFeedCache({ enabled: !initialTabFromState }) (use
the symbols useFeedCache, initialTabFromState, isRestoringFromCache, readCache,
initialCache) so that when initialTabFromState is set the hook does not read or
restore scroll state.
- Around line 65-150: The code duplicates data flows: keep a single source (the
manual state variables totalFeedPosts / myFeedPosts and their loaders
loadTotalFeeds / loadMyFeeds) and remove the separate useInfiniteScroll logic
that also calls the APIs; update renders to read from totalFeedPosts or
myFeedPosts (not the hook's items) and wire loadMoreFeeds as the only pagination
trigger (and remove the separate hook and its items/nextCursor usage).
Specifically, delete or disable the useInfiniteScroll block and any variables it
exposes, ensure loadTotalFeeds and loadMyFeeds set nextCursor/isLast
consistently, and update the render (where it referenced hook.items) to use
totalFeedPosts or myFeedPosts so cache restoration and single API path are
respected.
In `@src/pages/feed/FeedDetailPage.tsx`:
- Around line 166-167: In FeedDetailPage replace the early return that renders
only <Wrapper /> when (error || !feedData) so the back header remains visible;
update the error/empty branch to render the existing header/back component (the
same header used in the normal UI) together with the Wrapper and a minimal error
or empty-state message. Locate the conditional in FeedDetailPage and ensure you
include the header/back component (not just Wrapper) before returning for the
error or empty-data case.
---
Outside diff comments:
In `@src/pages/feed/FeedDetailPage.tsx`:
- Around line 36-62: The loadFeedDetail effect can apply stale responses to
state when feedId changes; modify the useEffect and its inner loadFeedDetail to
cancel or ignore previous requests by creating an AbortController (or a local
"isCurrent" flag) scoped to the effect, pass its signal into getFeedDetail (or
check the flag before calling setFeedData/setError), and in the cleanup return
set the controller.abort() (or flip the flag) so only the latest request updates
state (update references in useEffect, loadFeedDetail, getFeedDetail call site,
and the setFeedData/setError/setLoading usages).
- Around line 38-49: The current code only checks for a falsy feedId string and
can call getFeedDetail(Number(feedId)) with NaN; before calling getFeedDetail,
parse the id to a number and add a Number.isFinite check (e.g. const idNum =
Number(feedId); if (!Number.isFinite(idNum)) { setError('유효하지 않은 피드 ID입니다.');
setLoading(false); return; }) to early-return on invalid paths; update
subsequent calls to use idNum when calling getFeedDetail and keep existing
setError/setLoading behavior.
ℹ️ Review info
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
src/pages/feed/Feed.tsxsrc/pages/feed/FeedDetailPage.tsx
- FollowerListPage: 구버전 loadUserList useCallback 제거, useInifinieScroll 복원 - GroupSearch: 고아 setTimeout·loadMore useCallback 제거, searchFirstPage 대신 useInifinieScroll 기반 searchResult 도입 - Memory: fetchPage 내 loadMemoryPosts useCallback 혼입 코드 제거, 미사용 error/loading 상태 및 LoadingSpinner import 제거 - SavePage: 구버전 loadSavedBooks/loadSavedFeeds/loadAllData 수동 페이지네이션 코드 제거 (useInifinieScroll 중복) - SearchBook: 구버전 loadMore useCallback 잔재 제거, LoadingBox import 추가 - useFeedCache: writeFeedCache의 sessionStorage.setItem을 try/catch로 감싸 쿼터 초과·프라이빗 브라우징 환경에서 예외가 피드 렌더로 전파되지 않도록 보호
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/pages/feed/FollowerListPage.tsx (1)
69-99:⚠️ Potential issue | 🟠 Major목록 조회 오류가 사용자에게 노출되지 않습니다.
userList.error를 렌더링하지 않아 실패가 “빈 리스트”처럼 보입니다. 오류 상태 UI를 별도로 분기해 주세요.🔧 제안 수정
- {userList.isLoading && userList.items.length === 0 ? ( + {userList.error ? ( + <UserProfileList> + <div style={{ textAlign: 'center', padding: '24px 0' }}> + 목록을 불러오지 못했습니다. 잠시 후 다시 시도해 주세요. + </div> + </UserProfileList> + ) : userList.isLoading && userList.items.length === 0 ? ( <UserProfileList> {Array.from({ length: 5 }).map((_, i) => ( <UserProfileItemSkeleton key={i} type={type as UserProfileType} /> ))} </UserProfileList>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/feed/FollowerListPage.tsx` around lines 69 - 99, The UI hides backend errors by never rendering userList.error, so when fetching fails the page looks like an empty list; update FollowerListPage to branch on userList.error before the skeleton/items render (check userList.error from the same data source used for userList.isLoading/isLast/isLoadingMore) and render an explicit error state inside the UserProfileList (or replace it) with a readable message and retry affordance; ensure the error branch references the same symbols (userList.error, userList.sentinelRef, UserProfileList, UserProfileItemSkeleton, LoadingSpinner) and preserves existing loading and pagination UI for other branches.src/pages/searchBook/SearchBook.tsx (1)
258-296:⚠️ Potential issue | 🟠 Major피드 조회 실패가 “데이터 없음”으로 오인됩니다.
feeds.error를 렌더링 분기에 반영하지 않아, API 실패 시에도 빈 상태 문구가 노출됩니다. 실패 상태와 빈 결과 상태를 분리해 주세요.🔧 제안 수정
- {feeds.isLoading && feeds.items.length === 0 ? ( + {feeds.error ? ( + <EmptyState> + <EmptyTitle>피드를 불러오지 못했어요.</EmptyTitle> + <EmptySubText>{feeds.error}</EmptySubText> + </EmptyState> + ) : feeds.isLoading && feeds.items.length === 0 ? ( <FeedPostContainer> {Array.from({ length: 3 }).map((_, i) => ( <FeedPostSkeleton key={i} /> ))} </FeedPostContainer>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/searchBook/SearchBook.tsx` around lines 258 - 296, The conditional render for feed results treats API errors as "no data"; update the JSX branch that currently checks feeds.isLoading and feeds.items to first check feeds.error (e.g., if (feeds.error) ...) and render an error state/component/message instead of the EmptyState when feeds.error is truthy; ensure you still handle feeds.isLoading, feeds.items.length === 0, feeds.isLast, feeds.sentinelRef, and feeds.isLoadingMore as before and reference the same symbols (feeds.error, feeds.items, feeds.isLoading, feeds.isLast, feeds.sentinelRef, feeds.isLoadingMore, FeedPostContainer, EmptyState) so error vs empty result are clearly separated.src/pages/groupSearch/GroupSearch.tsx (1)
138-163:⚠️ Potential issue | 🔴 Critical검색어가 빠르게 바뀔 때 최신 쿼리 재조회가 누락됩니다.
reloadKey변경 시useInifinieScroll훅의 useEffect(lines 90-96)가 실행되어 기존 데이터를 클리어한 후loadFirstPage()를 호출합니다. 하지만 이전 요청이 진행 중이면loadFirstPage()의 가드문(line 54:if (!enabled || isFetchingRef.current) return;)에서 조기 종료되어 새 검색 조건의 요청이 시작되지 않습니다. 그 결과 데이터는 비워지지만 새 결과는 로드되지 않은 상태가 유지되다가, 이전 요청이 완료될 때 오래된 결과가 화면에 표시됩니다.GroupSearch 컴포넌트에서
reloadKey는debouncedSearchTerm,selectedFilter,category,searchStatus의 변화에 따라 자주 갱신되므로, 사용자가 빠르게 검색어를 입력할 때 이 문제가 발생할 가능성이 높습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/groupSearch/GroupSearch.tsx` around lines 138 - 163, The hook useInifinieScroll can leave an inflight fetch blocking loadFirstPage due to isFetchingRef, causing stale/empty UI when reloadKey changes; modify useInifinieScroll so that when reloadKey (or the effect that clears data) runs it aborts any in-progress request and resets isFetchingRef so a new loadFirstPage can start: add an AbortController per fetch request (created before calling the provided fetchPage), pass its signal into fetchPage or ensure fetchPage supports cancellation, call controller.abort() in the cleanup path triggered by reloadKey changes, and set isFetchingRef.current = false after abort so loadFirstPage's guard (if (!enabled || isFetchingRef.current) return) won’t short-circuit the new request; reference useInifinieScroll, loadFirstPage, isFetchingRef, fetchPage, and reloadKey when implementing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/pages/feed/FollowerListPage.tsx`:
- Around line 26-33: The current useInifinieScroll call always sets enabled:
true which triggers fetches for invalid route values; change the enabled flag to
only activate when the route param is a valid type (e.g., check type against an
allowed set like 'followerlist'/'followinglist') and userId is present as
needed; update the useInifinieScroll invocation (symbols: useInifinieScroll,
enabled, reloadKey, fetchPage, type, userId) so enabled becomes a boolean
expression that validates type (and userId where required) before running
fetchPage.
---
Outside diff comments:
In `@src/pages/feed/FollowerListPage.tsx`:
- Around line 69-99: The UI hides backend errors by never rendering
userList.error, so when fetching fails the page looks like an empty list; update
FollowerListPage to branch on userList.error before the skeleton/items render
(check userList.error from the same data source used for
userList.isLoading/isLast/isLoadingMore) and render an explicit error state
inside the UserProfileList (or replace it) with a readable message and retry
affordance; ensure the error branch references the same symbols (userList.error,
userList.sentinelRef, UserProfileList, UserProfileItemSkeleton, LoadingSpinner)
and preserves existing loading and pagination UI for other branches.
In `@src/pages/groupSearch/GroupSearch.tsx`:
- Around line 138-163: The hook useInifinieScroll can leave an inflight fetch
blocking loadFirstPage due to isFetchingRef, causing stale/empty UI when
reloadKey changes; modify useInifinieScroll so that when reloadKey (or the
effect that clears data) runs it aborts any in-progress request and resets
isFetchingRef so a new loadFirstPage can start: add an AbortController per fetch
request (created before calling the provided fetchPage), pass its signal into
fetchPage or ensure fetchPage supports cancellation, call controller.abort() in
the cleanup path triggered by reloadKey changes, and set isFetchingRef.current =
false after abort so loadFirstPage's guard (if (!enabled ||
isFetchingRef.current) return) won’t short-circuit the new request; reference
useInifinieScroll, loadFirstPage, isFetchingRef, fetchPage, and reloadKey when
implementing.
In `@src/pages/searchBook/SearchBook.tsx`:
- Around line 258-296: The conditional render for feed results treats API errors
as "no data"; update the JSX branch that currently checks feeds.isLoading and
feeds.items to first check feeds.error (e.g., if (feeds.error) ...) and render
an error state/component/message instead of the EmptyState when feeds.error is
truthy; ensure you still handle feeds.isLoading, feeds.items.length === 0,
feeds.isLast, feeds.sentinelRef, and feeds.isLoadingMore as before and reference
the same symbols (feeds.error, feeds.items, feeds.isLoading, feeds.isLast,
feeds.sentinelRef, feeds.isLoadingMore, FeedPostContainer, EmptyState) so error
vs empty result are clearly separated.
ℹ️ Review info
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
src/hooks/useFeedCache.tssrc/pages/feed/FollowerListPage.tsxsrc/pages/groupSearch/GroupSearch.tsxsrc/pages/memory/Memory.tsxsrc/pages/mypage/SavePage.tsxsrc/pages/searchBook/SearchBook.tsx
✅ Files skipped from review due to trivial changes (1)
- src/pages/mypage/SavePage.tsx
ljh130334
left a comment
There was a problem hiding this comment.
수고하셨습니다! 스크롤 복원ㄴ 문제를 sessionStorage 캐싱으로 해결한 접근 너무 좋은 것 같네요 ㅎㅎ TTL이나 디바운스 같은 엣지 케이스 챙긴 부분도 꼼꼼하신 것 같아요! 👍🏻 👍🏻 💯
heeeeyong
left a comment
There was a problem hiding this comment.
무한스크롤 데이터를 세션스토리지에 캐싱하는 것뿐만 아니라, scrollY까지 함께 저장해 페이지 복원 UX를 완성한 점이 좋았습니다. requestAnimationFrame을 통해 복원 타이밍을 늦춰준다는 새로운 내용도 배워갑니다👍
📝작업 내용
상세 변경 사항
스크롤 복원(rAF), 스크롤 디바운스 저장을 훅 내부에 캡슐화
error / !feedData 상태의 3개 분기도 하나로 통합
현재 활성 탭 기준으로 조건 변경: activeTab === '피드' ? totalFeedPosts.length > 0 : myFeedPosts.length > 0
개선전
2026-02-25.5.38.58.mov
개선후
2026-02-25.5.45.12.mov
Summary by CodeRabbit
새로운 기능
UI/UX 개선