Skip to content

Commit

Permalink
�GLUE-322-구독피드-무한스크롤-구현 (#74)
Browse files Browse the repository at this point in the history
* feat: 무한스크롤 구현

* fix: pr 리뷰 반영
  • Loading branch information
yeyounging authored Jun 11, 2024
1 parent f02a5a6 commit 8915b77
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 96 deletions.
4 changes: 2 additions & 2 deletions src/app/search/components/BlogList/BlogCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ export default function BlogCard({
return (
<Link
href={`/blog/${id}`}
className="flex flex-col items-center justify-start border border-[#979696] rounded-2 shadow-sm"
className="flex flex-col items-center justify-start border border-[#979696] rounded-2 shadow-sm group"
>
<div className="relative w-250 h-130 group">
<div className="relative w-250 h-130">
<Image
src={photoUrl}
alt="bg"
Expand Down
17 changes: 3 additions & 14 deletions src/app/search/components/BlogList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import { generateId } from '@/utils';
import Pagination from '@/app/subscribe/components/Pagination';
import BlogCard from './BlogCard';
import { useSearchContext } from '../SearchFetcher/SearchContext';

interface BlogListProps {
currentPage: number;
onPageChange: (page: number) => void;
}

export default function BlogList({ currentPage, onPageChange }: BlogListProps) {
export default function BlogList() {
const { blogInfoItem } = useSearchContext();
const { blogInfoList, isFirst, isLast } = blogInfoItem;
const { blogInfoList } = blogInfoItem;

return (
<div>
<div className="mt-30">
Expand All @@ -26,12 +21,6 @@ export default function BlogList({ currentPage, onPageChange }: BlogListProps) {
))}
</div>
</div>
<Pagination
isFirst={isFirst}
isLast={isLast}
currentPage={currentPage}
onPageChange={onPageChange}
/>
</div>
);
}
61 changes: 23 additions & 38 deletions src/app/search/components/ContentList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,37 @@
import Story from '@/components/Story';
import Image from 'next/image';
import Pagination from '@/app/subscribe/components/Pagination';
import Link from 'next/link';
import { useSearchContext } from '../SearchFetcher/SearchContext';

interface ContentListProps {
currentPage: number;
onPageChange: (page: number) => void;
}

export default function ContentList({
currentPage,
onPageChange,
}: ContentListProps) {
export default function ContentList() {
const { postSearchItem } = useSearchContext();
const { postSearchList, isFirst, isLast } = postSearchItem;

return (
<div>
<div className="flex flex-col gap-30">
{postSearchList.map(({ blogId, title, postId, preview }) => (
<Link
href={`/blog/${blogId}/post/${postId}`}
key={postId}
className="flex flex-row gap-10 cursor-pointer"
>
<div className="absolute w-150 h-150">
{/* TODO: photoUrl 적용 */}
<Image
src="/tempImage/4.jpg"
alt={String(postId)}
layout="fill"
objectFit="cover"
className="rounded-3"
/>
</div>
<div className="relative pl-170 py-1">
<Story key={postId} title={title} content={preview} />
</div>
</Link>
))}
{postSearchItem.postSearchList.map(
({ blogId, title, postId, preview }) => (
<Link
href={`/blog/${blogId}/post/${postId}`}
key={postId}
className="flex flex-row gap-10 cursor-pointer"
>
<div className="absolute w-150 h-150">
<Image
src="/tempImage/4.jpg"
alt={String(postId)}
layout="fill"
objectFit="cover"
className="rounded-3"
/>
</div>
<div className="relative pl-170 py-1">
<Story key={postId} title={title} content={preview} />
</div>
</Link>
),
)}
</div>
<Pagination
isFirst={isFirst}
isLast={isLast}
currentPage={currentPage}
onPageChange={onPageChange}
/>
</div>
);
}
2 changes: 0 additions & 2 deletions src/app/search/components/SearchFetcher/SearchContext.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
'use client';

import { generateContext } from '@/react-utils';
import { SearchResponse } from './types';

Expand Down
80 changes: 71 additions & 9 deletions src/app/search/components/SearchFetcher/index.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,87 @@
import { ReactNode, useEffect } from 'react';
import { ReactNode, useCallback, useMemo } from 'react';
import { useIntersect } from '@/hooks';
import { SearchResultProvider } from './SearchContext';
import { useSearchResult } from './quries';
import { useSearchResult } from './queries';
import { SearchResponse } from './types';

interface SearchResultFetcherProps {
children: ReactNode;
page: number;
size: number;
keyword: string;
}

export function SearchResultFetcher({
children,
page,
size,
keyword,
}: SearchResultFetcherProps) {
const { data, refetch } = useSearchResult(page, size, keyword);
const { data, isLoading, fetchNextPage, hasNextPage } = useSearchResult(
keyword,
size,
);

useEffect(() => {
refetch();
}, [keyword, refetch]);
const handleIntersect = useCallback(
(entry: IntersectionObserverEntry) => {
if (entry.isIntersecting && hasNextPage && !isLoading) {
fetchNextPage();
}
},
[isLoading, hasNextPage, fetchNextPage],
);

return <SearchResultProvider {...data}>{children}</SearchResultProvider>;
const endOfListRef = useIntersect(handleIntersect);
const pages = data?.pages || [];
const mergedPostSearchItem = pages.reduce(
(acc, { result }) => {
acc.postSearchList.push(...result.postSearchItem.postSearchList);
acc.listSize += result.postSearchItem.listSize;
acc.totalPage = result.postSearchItem.totalPage;
acc.totalElements = result.postSearchItem.totalElements;
acc.hasNext = result.postSearchItem.hasNext;
return acc;
},
{
postSearchList: [] as SearchResponse['postSearchItem']['postSearchList'],
listSize: 0,
totalPage: 0,
totalElements: 0,
isFirst: true,
isLast: false,
hasNext: false,
},
);

const mergedBlogInfoItem = pages.reduce(
(acc, { result }) => {
acc.blogInfoList.push(...result.blogInfoItem.blogInfoList);
acc.listSize += result.blogInfoItem.listSize;
acc.totalPage = result.blogInfoItem.totalPage;
acc.totalElements = result.blogInfoItem.totalElements;
acc.hasNext = result.blogInfoItem.hasNext;
return acc;
},
{
blogInfoList: [] as SearchResponse['blogInfoItem']['blogInfoList'],
listSize: 0,
totalPage: 0,
totalElements: 0,
isFirst: true,
isLast: false,
hasNext: false,
},
);

const mergedData = useMemo(() => {
return {
postSearchItem: mergedPostSearchItem,
blogInfoItem: mergedBlogInfoItem,
};
}, [mergedPostSearchItem, mergedBlogInfoItem]);

return (
<SearchResultProvider {...mergedData}>
{children}
<div ref={endOfListRef} />
</SearchResultProvider>
);
}
16 changes: 16 additions & 0 deletions src/app/search/components/SearchFetcher/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useInfiniteQuery } from '@tanstack/react-query';
import { getSearchResult } from './api';

export const useSearchResult = (keyword: string, size: number) => {
const { data, isLoading, ...rest } = useInfiniteQuery({
queryKey: ['search-result', keyword],
queryFn: ({ pageParam }) => getSearchResult(pageParam, keyword, size),
initialPageParam: 0,
getNextPageParam: (lastPage, allPages) => {
const nextPage = allPages.length;
return lastPage.result.postSearchItem.hasNext ? nextPage : undefined;
},
refetchOnMount: false,
});
return { data, isLoading, ...rest };
};
12 changes: 0 additions & 12 deletions src/app/search/components/SearchFetcher/quries.ts

This file was deleted.

23 changes: 4 additions & 19 deletions src/app/search/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useEffect, useState } from 'react';
import { useState } from 'react';
import { Button } from '@/components/Common';
import { useSearchParams } from 'next/navigation';
import { AsyncBoundaryWithQuery } from '@/react-utils';
Expand All @@ -9,7 +9,6 @@ import { SearchResultFetcher } from './components/SearchFetcher';
import SearchFallback from './components/SearchFallback';

export default function Page() {
const [currentPage, setCurrentPage] = useState<number>(0);
const [postView, setPostView] = useState(true);
const searchParams = useSearchParams();
const query = searchParams.get('query') as string;
Expand All @@ -20,36 +19,22 @@ export default function Page() {
setPostView((prev) => !prev);
};

useEffect(() => {
setCurrentPage(0);
}, [query]);

return (
<div className="px-200 text-[#171717]">
<SearchBox />
<div className="flex flex-row justify-end px-10">
<Button
className="bg-transparent text-primary font-semibold underline underline-offset-4"
onClick={() => toggleView()}
onClick={toggleView}
>
{buttonText}
</Button>
</div>
<div className="p-30 py-40 bg-[#c78f6a] bg-opacity-10 rounded-5 shadow-lg">
<AsyncBoundaryWithQuery>
<SearchFallback>
<SearchResultFetcher page={currentPage} size={10} keyword={query}>
{postView ? (
<ContentList
currentPage={currentPage}
onPageChange={setCurrentPage}
/>
) : (
<BlogList
currentPage={currentPage}
onPageChange={setCurrentPage}
/>
)}
<SearchResultFetcher size={10} keyword={query}>
<div>{postView ? <ContentList /> : <BlogList />}</div>
</SearchResultFetcher>
</SearchFallback>
</AsyncBoundaryWithQuery>
Expand Down

0 comments on commit 8915b77

Please sign in to comment.