Skip to content

fix: 책 검색 무한스크롤 로직 수정 및 최근검색어 API 수정#164

Merged
heeeeyong merged 4 commits intodevelopfrom
chore/minor-updates
Aug 19, 2025
Merged

fix: 책 검색 무한스크롤 로직 수정 및 최근검색어 API 수정#164
heeeeyong merged 4 commits intodevelopfrom
chore/minor-updates

Conversation

@heeeeyong
Copy link
Collaborator

@heeeeyong heeeeyong commented Aug 18, 2025

#️⃣연관된 이슈

없음

📝작업 내용

  • 책 검색 무한스크롤 로직을 수정했습니다. 검색 완료 단계의 화면에서 전체 책 수가 제대로 반영되도록 하였습니다.
  • 최근검색어 삭제 API 요청 쿼리파라미터에서 userId가 더이상 필요하지않아서 삭제했습니다.

스크린샷

image

💬리뷰 요구사항

무한스크롤이 약간 버벅이는 현상이 느껴지는데 이 부분을 해결해야할 것 같습니다.

Summary by CodeRabbit

  • New Features

    • 검색 결과에 전체 결과 수(total count)가 표시됩니다.
  • Refactor

    • 무한 스크롤 로딩이 개선되어 다음 페이지가 자동으로 매끄럽게 로드되고 더보기 가능 여부 판단이 정확해졌습니다.
    • 초기 진입 시 최근 검색이 자동으로 불러와집니다.
    • 최근 검색 삭제 흐름이 단순화되어 더 안정적으로 동작합니다.
    • 새 검색 시작 시 상태가 초기화되어 일관된 결과를 제공합니다.

@vercel
Copy link

vercel bot commented Aug 18, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
thip Ready Ready Preview Comment Aug 18, 2025 8:03pm

@coderabbitai
Copy link

coderabbitai bot commented Aug 18, 2025

Walkthrough

최근검색 삭제 API에서 userId 파라미터가 제거되었고 호출부들이 이에 맞춰 단일 인자로 변경되었습니다. 검색 화면은 무한 스크롤과 totalElements 반영 로직이 추가/개편되었고, BookSearchResult는 totalElements 프로퍼티로 헤더 카운트를 표시합니다.

Changes

Cohort / File(s) Change Summary
API: 최근검색 삭제
src/api/recentsearch/deleteRecentSearch.ts
함수 시그니처에서 userId 제거; DELETE 요청 경로를 /recent-searches/{recentSearchId}로 변경. 반환 타입과 에러 처리 유지.
검색 페이지: 무한 스크롤/카운트
src/pages/search/Search.tsx
totalElements 상태 추가 및 초기 응답 반영. IntersectionObserver 기반 lastBookElementCallback 및 useCallback loadMore 도입. 페이지네이션 로직 재구성, hasMore를 응답의 last로 결정. recentSearches 초기 로드(useEffect) 재도입. BookSearchResult에 totalElements 전달.
컴포넌트: 결과 헤더 카운트
src/components/search/BookSearchResult.tsx
Props에 totalElements?: number 추가. 검색 중이 아닐 때 헤더 카운트를 리스트 길이 대신 totalElements로 표시. BookItem key를 book.isbn + index로 변경.
호출부 정리: 최근검색 삭제
src/pages/feed/UserSearch.tsx, src/pages/groupSearch/GroupSearch.tsx
deleteRecentSearch 호출에서 하드코딩/로컬 userId 제거; 단일 인자(recentSearchId)로 호출하도록 변경.

Sequence Diagram(s)

sequenceDiagram
  participant U as 사용자
  participant UI as Search.tsx
  participant API as 검색 API
  participant Comp as BookSearchResult

  U->>UI: 키워드 입력/검색 실행
  UI->>API: getSearchBooks(term, page=1, manual?)
  API-->>UI: { content, last, totalElements }
  UI->>UI: setResults, setHasMore(!last), setTotalElements
  UI->>Comp: render(results, totalElements, lastBookElementCallback)

  Note over UI,Comp: IntersectionObserver로 sentinel 관찰

  Comp-->>UI: lastBookElementCallback(교차 감지)
  UI->>UI: if (hasMore && !isLoadingMore) loadMore()
  UI->>API: getSearchBooks(term, page+1)
  API-->>UI: { content, last }
  UI->>UI: appendResults, setHasMore(!last), setPage(page+1)
  UI->>Comp: re-render
Loading
sequenceDiagram
  participant U as 사용자
  participant UI as UserSearch/GroupSearch
  participant API as RecentSearch API

  U->>UI: 최근검색 항목 삭제 클릭
  UI->>API: DELETE /recent-searches/{recentSearchId}
  API-->>UI: 200 OK
  UI->>UI: 상태에서 해당 항목 제거 후 갱신
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • ljh130334

Poem

귀 쫑긋, 스크롤 톡—끝이 아니야!
totalElements가 길을 속삭이네.
페이지를 더하면 결과가 쌓이고,
userId는 후다닥 사라졌지.
옵저버가 부르면 토끼는 다시 달려가. 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/minor-updates

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@heeeeyong heeeeyong self-assigned this Aug 18, 2025
@heeeeyong heeeeyong requested a review from ho0010 August 18, 2025 19:25
@heeeeyong heeeeyong added ✨ Feature 기능 개발 📬 API 서버 API 통신 🐞 BugFix Something isn't working labels Aug 18, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
src/components/search/BookSearchResult.tsx (1)

37-37: totalElements 미지정 시 '전체 undefined' 노출 가능 — 안전한 폴백 및 표시 최적화 제안

API에서 totalElements가 아직 세팅되지 않았거나 전달되지 않는 경우가 있을 수 있어 '전체 undefined'가 노출될 수 있습니다. 검색 결과 배열 길이를 폴백으로 사용하고, 숫자 포매팅(+ 단위)을 적용하면 사용자 경험이 좋아집니다.

아래처럼 처리하는 것을 제안합니다.

-        {type === 'searching' ? <></> : <ResultHeader>전체 {totalElements}</ResultHeader>}
+        {type === 'searching' ? (
+          <></>
+        ) : (
+          <ResultHeader>
+            전체 {(typeof totalElements === 'number' ? totalElements : searchedBookList.length).toLocaleString()}권
+          </ResultHeader>
+        )}
src/pages/search/Search.tsx (3)

63-93: loadMore 흐름 전반은 적절하나, jank 완화에는 '고유 key 보장'이 우선

요청 중복 방지(isLoadingMore), 종료 여부(hasMore) 가드는 적절합니다. 다만 현재 무한스크롤 버벅임의 주요 원인은 리스트 key 중복 가능성(페이지 append 시 id 재사용)입니다. BookSearchResult에서 key를 isbn으로 바꾸는 대응을 최우선으로 적용해 주세요. 적용 후에도 미세한 버벅임이 남는다면 IntersectionObserver 프리패치 폭을 확대하는 최적화를 병행하세요(아래 코멘트 참조).


95-112: IntersectionObserver 프리패치 폭 확대로 스크롤 버벅임 완화

뷰포트에 거의 닿은 뒤에야 요청이 나가면 사용자 체감이 끊깁니다. rootMargin을 넉넉히 주고 threshold를 낮춰 사전 로드를 유도하세요. 또한 동일 노드에 대해 불필요한 disconnect/observe 반복을 줄이는 것도 도움이 됩니다.

아래처럼 옵션 추가를 권장합니다.

-      observerRef.current = new IntersectionObserver(entries => {
-        if (entries[0].isIntersecting && hasMore && !isLoadingMore) {
-          loadMore();
-        }
-      });
+      observerRef.current = new IntersectionObserver(
+        entries => {
+          if (entries[0].isIntersecting && hasMore && !isLoadingMore) {
+            loadMore();
+          }
+        },
+        {
+          root: null,
+          rootMargin: '300px 0px', // 뷰포트 300px 전에 미리 로드
+          threshold: 0.01,
+        },
+      );

추가로, lastBookElementRef가 현재 읽히지 않습니다. 사용하지 않는다면 정리하는 편이 낫습니다.


122-123: 새 검색 시작 시 totalElements 초기화 권장

새 키워드 입력 시 이전 검색의 totalElements가 일시적으로 남아 있을 수 있습니다(특히 빠른 전환 시). 헤더 폴백을 적용하더라도 상태를 0으로 초기화하면 잔여 표기 가능성을 줄일 수 있습니다.

   setHasMore(true); // 새로운 검색 시 hasMore 초기화
   setPage(1); // 페이지를 1로 초기화
+  setTotalElements(0); // 총 개수 초기화
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 62c2187 and f281813.

📒 Files selected for processing (5)
  • src/api/recentsearch/deleteRecentSearch.ts (1 hunks)
  • src/components/search/BookSearchResult.tsx (3 hunks)
  • src/pages/feed/UserSearch.tsx (1 hunks)
  • src/pages/groupSearch/GroupSearch.tsx (1 hunks)
  • src/pages/search/Search.tsx (7 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/pages/groupSearch/GroupSearch.tsx (1)
src/api/recentsearch/deleteRecentSearch.ts (1)
  • deleteRecentSearch (11-23)
src/pages/feed/UserSearch.tsx (1)
src/api/recentsearch/deleteRecentSearch.ts (1)
  • deleteRecentSearch (11-23)
src/pages/search/Search.tsx (2)
src/api/books/getSearchBooks.ts (2)
  • getSearchBooks (41-59)
  • convertToSearchedBooks (61-70)
src/api/recentsearch/deleteRecentSearch.ts (1)
  • deleteRecentSearch (11-23)
🔇 Additional comments (11)
src/pages/feed/UserSearch.tsx (1)

64-65: 삭제 API 시그니처 변경 반영 완료

userId 제거된 새로운 시그니처에 맞게 호출부가 정리되었습니다. 후속 흐름(상태 업데이트, 에러 로깅)도 일관적입니다.

src/pages/groupSearch/GroupSearch.tsx (1)

292-299: 삭제 API 호출부 정리 OK

하드코딩된 userId 제거 및 단일 인자 호출로 정돈되었습니다. 응답 처리도 기존 로직과 합치됩니다.

src/api/recentsearch/deleteRecentSearch.ts (1)

16-17: 엔드포인트 정정 LGTM

쿼리 파라미터 제거 후 RESTful한 경로로 정리되어 명확합니다. 에러 로깅/전파도 기존대로 적절합니다.

src/pages/search/Search.tsx (8)

33-34: totalElements 상태 추가 LGTM

총 개수를 별도 상태로 관리하여 헤더 표시와 분리한 방향성 좋습니다.


36-37: 페이지를 1부터 시작하도록 명시 — 적절합니다

서버/UX 기준에 맞춘 명시적 초기화로 혼선을 줄일 수 있습니다.


113-116: 최근검색어 초기 로딩 훅 복구 OK

마운트 시점 fetch로 UX를 보완합니다. 에러 시 빈 배열 처리도 일관적입니다.


160-161: 검색 시작 시 페이지/hasMore 초기화 적절

초기 페이지를 고정하고 hasMore를 리셋하는 흐름은 안정적입니다.


164-165: isFinalized 파라미터 전달 의도 재확인 권장

현재 isManualSearch 값을 그대로 isFinalized로 API에 전달합니다. "사용자가 명시적으로 검색 버튼을 눌렀을 때만(isFinalized=true) 최근검색어 기록" 등의 목적이라면 합리적입니다. 백엔드 기대값과 일치하는지 한 번만 확인해 주세요.


170-172: hasMore/totalElements 서버 값 반영 LGTM

서버의 last/totalElements를 신뢰해 상태를 갱신하는 방식은 페이징 상태와 헤더 표기를 정확히 유지합니다.


212-213: 최근검색어 삭제 API 시그니처 변경 반영 OK

단일 인자 호출로 간소화되었고, 성공 시 상태 필터링도 일관적으로 동작합니다.


295-296: BookSearchResult로 totalElements 전달 OK — 헤더 폴백과 병행 권장

props 전달은 적절합니다. 상단 코멘트의 폴백/포매팅까지 함께 적용하면 일시적 값 부재에도 안전합니다.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/components/search/BookSearchResult.tsx (1)

48-48: key에 index 포함은 잠재적 재마운트/DIFF 비용 증가 — ISBN 단독 키 권장

이전 리뷰에서도 안내된 내용과 유사합니다. index를 키에 섞으면 중간 삽입/정렬/필터링 시 컴포넌트 재마운트가 늘어나고, 가상 DOM diff 효율이 떨어질 수 있습니다. ISBN이 고유하다면 ISBN 단독 키가 가장 안정적입니다.

수정 제안:

-              key={`book-${book.isbn}-${index}`}
+              key={book.isbn}

만약 API 특성상 동일 ISBN이 중복으로 내려오는 경우가 있다면, 렌더 전 단계에서 de-dup을 하거나, 부득이한 경우에만 보조 식별자를 결합하는 방식을 고려해 주세요.

🧹 Nitpick comments (3)
src/components/search/BookSearchResult.tsx (3)

46-56: 이미지 lazy-loading/async decoding으로 스크롤 버벅임 완화

무한스크롤에서 다량의 이미지 로딩은 주요 jank 원인입니다. 이미지에 lazy/async 속성을 부여하면 즉시 체감 성능이 개선됩니다.

부분 수정 제안:

-              <Cover src={book.coverUrl} alt={`${book.title} 커버`} />
+              <Cover src={book.coverUrl} alt={`${book.title} 커버`} loading="lazy" decoding="async" />

또는 styled 정의에서 기본 attrs로 강제하는 방법:

// 대체안: styled 정의 자체를 attrs로 교체
const Cover = styled.img.attrs({ loading: 'lazy', decoding: 'async' })`
  width: 80px;
  height: 107px;
  object-fit: cover;
`;

46-55: [선택] 결과가 많다면 리스트 가상화(windowing) 도입 고려

무한스크롤에서 DOM 노드 수가 커지면 어떤 최적화보다 windowing 효과가 큽니다. react-window/react-virtuoso 도입 시 렌더/리플로우/이미지 로딩 모두 체감 감소합니다. 도입 난이도 대비 성능 이득이 커서, 버벅임 이슈가 지속되면 우선순위 높게 고려를 권장합니다.


12-22: BookSearchResult의 totalElements를 필수 Prop로 변경 권장

검색 페이지에서는 항상 totalElements를 전달하고 있으므로, BookSearchResultProps에서 optional 처리(totalElements?: number)를 제거하고 필수로 정의하는 편이 안전합니다. 필요하다면 컴포넌트 시그니처에 기본값(totalElements = 0)을 지정하세요.

  • 파일: src/components/search/BookSearchResult.tsx
    • type BookSearchResultPropstotalElements?: numbertotalElements: number 로 수정
    • (선택) 함수 파라미터 디폴트: totalElements = 0 추가
  • 변경 후 Search.tsx 호출부에서 타입 오류가 없는지 확인
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f281813 and a2aa143.

📒 Files selected for processing (1)
  • src/components/search/BookSearchResult.tsx (4 hunks)

<Wrapper>
<List>
{type === 'searching' ? <></> : <ResultHeader>전체 {searchedBookList.length}</ResultHeader>}
{type === 'searching' ? <></> : <ResultHeader>전체 {totalElements}</ResultHeader>}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

전체 undefined 표시 가능 — totalElements 미전달/초기값 시 Fallback 필요

totalElements가 optional인데 바로 출력되고 있어, 값이 아직 준비되지 않은 렌더 타이밍에 "전체 undefined"가 노출될 수 있습니다. length를 안전한 Fallback으로 사용해 주세요.

적용 제안:

-        {type === 'searching' ? <></> : <ResultHeader>전체 {totalElements}</ResultHeader>}
+        {type === 'searching' ? null : (
+          <ResultHeader>전체 {totalElements ?? searchedBookList.length}</ResultHeader>
+        )}
🤖 Prompt for AI Agents
In src/components/search/BookSearchResult.tsx around line 37, totalElements is
optional and may be undefined during initial render causing "전체 undefined" to
appear; update the rendering to use a safe fallback (e.g., display totalElements
?? items?.length ?? 0) so that when totalElements is not provided it falls back
to the items array length (or 0) before rendering the ResultHeader.

@ho0010
Copy link
Collaborator

ho0010 commented Aug 19, 2025

LGTM!
수정사항 확인했습니다. 감사합니다

@heeeeyong heeeeyong merged commit 327fbb8 into develop Aug 19, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📬 API 서버 API 통신 🐞 BugFix Something isn't working ✨ Feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants