Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/api/recentsearch/deleteRecentSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@ export interface DeleteRecentSearchResponse {

export const deleteRecentSearch = async (
recentSearchId: number,
userId: number,
): Promise<DeleteRecentSearchResponse> => {
try {
const response = await apiClient.delete<DeleteRecentSearchResponse>(
`/recent-searches/${recentSearchId}?userId=${userId}`,
`/recent-searches/${recentSearchId}`,
);
return response.data;
} catch (error) {
Expand Down
6 changes: 4 additions & 2 deletions src/components/search/BookSearchResult.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface BookSearchResultProps {
hasMore?: boolean;
isLoading?: boolean;
lastBookElementCallback?: (node: HTMLDivElement | null) => void;
totalElements?: number;
}

export function BookSearchResult({
Expand All @@ -17,6 +18,7 @@ export function BookSearchResult({
hasMore = false,
isLoading = false,
lastBookElementCallback,
totalElements,
}: BookSearchResultProps) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
const navigate = useNavigate();

Expand All @@ -32,7 +34,7 @@ export function BookSearchResult({
return (
<Wrapper>
<List>
{type === 'searching' ? <></> : <ResultHeader>전체 {searchedBookList.length}</ResultHeader>}
{type === 'searching' ? <></> : <ResultHeader>전체 {totalElements}</ResultHeader>}
Copy link
Copy Markdown

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.


{isEmptySearchedBookList() ? (
<EmptyWrapper>
Expand All @@ -43,7 +45,7 @@ export function BookSearchResult({
) : (
searchedBookList.map((book, index) => (
<BookItem
key={book.id}
key={`book-${book.isbn}-${index}`}
onClick={() => navigate(`/search/book/${book.isbn}`)}
ref={
index === searchedBookList.length - 1 && lastBookElementCallback
Expand Down
4 changes: 1 addition & 3 deletions src/pages/feed/UserSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,7 @@ const UserSearch = () => {

const handleDelete = async (recentSearchId: number) => {
try {
const userId = 1; // 임시 userId

const response = await deleteRecentSearch(recentSearchId, userId);
const response = await deleteRecentSearch(recentSearchId);

if (response.isSuccess) {
setRecentSearches(prev => prev.filter(item => item.recentSearchId !== recentSearchId));
Expand Down
3 changes: 1 addition & 2 deletions src/pages/groupSearch/GroupSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,7 @@ const GroupSearch = () => {
handleDelete={(term: string) => {
const x = recentSearches.find(i => i.searchTerm === term);
if (!x) return;
const userId = 1;
deleteRecentSearch(x.recentSearchId, userId).then(res => {
deleteRecentSearch(x.recentSearchId).then(res => {
if (res.isSuccess) {
setRecentSearches(prev =>
prev.filter(it => it.recentSearchId !== x.recentSearchId),
Expand Down
72 changes: 36 additions & 36 deletions src/pages/search/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ const Search = () => {
const [searchResults, setSearchResults] = useState<SearchedBook[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [isInitialized, setIsInitialized] = useState(false);

const [totalElements, setTotalElements] = useState(0);
// 무한스크롤 관련 상태
const [hasMore, setHasMore] = useState(true);
const [page, setPage] = useState(1);
const [page, setPage] = useState(1); // 1부터 시작
const [isLoadingMore, setIsLoadingMore] = useState(false);

const [recentSearches, setRecentSearches] = useState<RecentSearchData[]>([]);
Expand All @@ -59,30 +59,8 @@ const Search = () => {
}
};

useEffect(() => {
fetchRecentSearches();
}, []);

// 무한스크롤을 위한 Intersection Observer 설정
const lastBookElementCallback = useCallback(
(node: HTMLDivElement | null) => {
if (isLoadingMore || !hasMore) return;

if (observerRef.current) observerRef.current.disconnect();

observerRef.current = new IntersectionObserver(entries => {
if (entries[0].isIntersecting && hasMore && !isLoadingMore) {
loadMore();
}
});
if (node) observerRef.current.observe(node);
lastBookElementRef.current = node;
},
[isLoadingMore, hasMore],
);

// 추가 데이터 로드 함수
const loadMore = async () => {
const loadMore = useCallback(async () => {
if (!searchTerm.trim() || isLoadingMore || !hasMore) return;

try {
Expand All @@ -97,8 +75,8 @@ const Search = () => {
if (newResults.length > 0) {
setSearchResults(prev => [...prev, ...newResults]);
setPage(nextPage);
// 더 이상 데이터가 없으면 hasMore를 false로 설정
setHasMore(newResults.length === 10); // size가 10이므로
// API 응답의 last 필드를 사용하여 hasMore 설정
setHasMore(!response.data.last);
} else {
setHasMore(false);
}
Expand All @@ -112,14 +90,36 @@ const Search = () => {
} finally {
setIsLoadingMore(false);
}
};
}, [searchTerm, isLoadingMore, hasMore, page, isFinalized]);

// 무한스크롤을 위한 Intersection Observer 설정
const lastBookElementCallback = useCallback(
(node: HTMLDivElement | null) => {
if (isLoadingMore || !hasMore) return;

if (observerRef.current) observerRef.current.disconnect();

observerRef.current = new IntersectionObserver(entries => {
if (entries[0].isIntersecting && hasMore && !isLoadingMore) {
loadMore();
}
});
if (node) observerRef.current.observe(node);
lastBookElementRef.current = node;
},
[isLoadingMore, hasMore, loadMore],
);

useEffect(() => {
fetchRecentSearches();
}, []);

const handleChange = (value: string) => {
setSearchTerm(value);
setIsFinalized(false);
setIsSearching(value.trim() !== '');
setHasMore(true); // 새로운 검색 시 hasMore 초기화
setPage(1); // 페이지 초기화
setPage(1); // 페이지를 1로 초기화

if (value.trim()) {
setSearchParams({ q: value.trim() }, { replace: true });
Expand Down Expand Up @@ -157,17 +157,18 @@ const Search = () => {
}

setIsLoading(true);
setPage(1); // 검색 시 페이지 초기화
setPage(1); // 검색 시 페이지를 1로 초기화
setHasMore(true); // 검색 시 hasMore 초기화

try {
const response = await getSearchBooks(term, 1, isManualSearch);
const response = await getSearchBooks(term, 1, isManualSearch); // 첫 페이지는 1

if (response.isSuccess) {
const convertedResults = convertToSearchedBooks(response.data.searchResult);
setSearchResults(convertedResults);
// 더 이상 데이터가 없으면 hasMore를 false로 설정
setHasMore(convertedResults.length === 10); // size가 10이므로
// API 응답의 last 필드를 사용하여 hasMore 설정
setHasMore(!response.data.last);
setTotalElements(response.data.totalElements);
} else {
console.log('검색 실패:', response.message);
setSearchResults([]);
Expand Down Expand Up @@ -208,9 +209,7 @@ const Search = () => {

const handleDelete = async (recentSearchId: number) => {
try {
const userId = 1; // 임시 userId

const response = await deleteRecentSearch(recentSearchId, userId);
const response = await deleteRecentSearch(recentSearchId);

if (response.isSuccess) {
setRecentSearches(prev => prev.filter(item => item.recentSearchId !== recentSearchId));
Expand Down Expand Up @@ -293,6 +292,7 @@ const Search = () => {
hasMore={hasMore}
isLoading={isLoadingMore}
lastBookElementCallback={lastBookElementCallback}
totalElements={totalElements}
/>
)}
</>
Expand Down