무한스크롤 구현하기

Ahyeon, Jung·2023년 12월 4일
0
post-custom-banner

높이에 따른 데이터 불러오기

사용자가 눈으로 보고 있는 화면의 높이(clientHeight)와 사용자가 스크롤해서 내린만큼의 높이(scrollTop)를 더한 값이, 전체화면에 대한 높이(scrollHeight)와 같으면 새로운 데이터를 받아오는 방식

window.addEventListner("scroll", infiniteScroll);
fetch(`${API}/posts?offset=${offset}&limit=${limit}`)
  .then(res => res.json())
  .then(res => {
    setState({
      products: res.products,
      offset: offset + limit,
      });
    });
    
const infiniteScroll = () => {
  const scrollHeight = Math.max(
    document.documentElement.scrollHeight,
    document.body.scrollHeight
  );
  const scrollTop = Math.max(
    document.documentElement.scrollTop,
    document.body.scrollTop
  );
  const clientHeight = document.documentElement.clientHeight;
  if (scrollTop + clientHeight === scrollHeight) {
    fetch(`${API}/posts?offset=${offset}&limit=${limit}`)
      .then(res => res.json)
      .then(res => {
        setState({
          products: [...products, ...res.products],
          offset: offset + limit,
        });
        setState({ isLoading: true});
        setTimeout(() => {
          setState({ isLoading: false });
        }, 350);
      });
    }

요소에 따른 데이터 불러오기

타겟 요소와 타겟의 부모 혹은 상위 요소의 뷰포트가 교차되는 부분을 비동기적으로 관찰하는 API, IntersectionObserver를 활용하여 화면에 지정한 타겟 요소가 보이면 데이터를 불러옴

function ProductPage () {
  const [ target, setTarget ] = useState(null);
// ...

  const _fetchProductItems = () => {
    const productItems = apiProductItems(itemLength);

    if (!productItems.length) {
      actions.isLoaded(dispatch)(false);
      return;
    }

    // ...
  };

  useEffect(() => {
    let observer;
    if (target) {
      observer = new IntersectionObserver(_onIntersect, { threshold: 1 });
      observer.observe(target);
    }

    return () => observer && observer.disconnect();
  }, [ target ]);

  const _onIntersect = ([ entry ]) => {
    if (entry.isIntersecting) {
      _fetchProductItems();
    }
  };

// ...

  return (
    <>
      // ...
      {state.isLoaded && <div ref={setTarget}>loading</div>}
    </>
  );
}

export default ProductPage;

useInfiniteQuery

tanstack-query의 useInfiniteQuery를 활용한 무한 스크롤

const {
  fetchNextPage,
  fetchPreviousPage,
  hasNextPage,
  hasPreviousPage,
  isFetchingNextPage,
  isFetchingPreviousPage,
  ...result
} = useInfiniteQuery({
  queryKey,
  queryFn: ({ pageParam }) => fetchPage(pageParam),
  initialPageParam: 1,
  ...options,
  getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) =>
    lastPage.nextCursor,
  getPreviousPageParam: (firstPage, allPages, firstPageParam, allPageParams) =>
    firstPage.prevCursor,
})
import PostItem from '../../../components/common/PostItem';
import styles from '../styles/Main.News.module.scss';
import {
  QueryFunction,
  QueryFunctionContext,
  useInfiniteQuery,
} from '@tanstack/react-query';
import { instance } from '@/api/instance';

interface PostsDataType {
  pages: {
    totalPage: number;
    currentPage: number;
    posts: {
      id: number;
      title: string;
      content: string;
      createdAt: string;
      updatedAt: string;
      authorId: number;
      viewCount: number;
      postImg: null | string;
    }[];
  }[];
}
export interface IRepository {
  total_count: number;
  incomplete_results: boolean;
  items: PostsDataType[];
  nextCursor?: string;
  prevCursor?: string;
}

type QueryKey = [string, string, string];

const News = () => {
  const page = 1;

  const fetchRepositories: QueryFunction<
    IRepository,
    QueryKey,
    unknown
  > = async ({ queryKey }: QueryFunctionContext<QueryKey>) => {
    const [, page, search] = queryKey;
    return await instance
      .get<IRepository>(
        `/post/list?page=${page}&limit=7${search && `&search=${search}`}`,
      )
      .then((res) => res.data);
  };
  const {
    fetchNextPage,
    fetchPreviousPage,
    hasNextPage,
    hasPreviousPage,
    isFetchingNextPage,
    isFetchingPreviousPage,
    ...result
  } = useInfiniteQuery<IRepository, Error, PostsDataType>({
    queryKey: ['postList', page.toString(), 'all'],
    queryFn: fetchRepositories,
    getNextPageParam: (lastPage) => lastPage.nextCursor,
    getPreviousPageParam: (firstPage) => firstPage.prevCursor,
  });

  console.log(result?.data);
  console.log(
    fetchNextPage,
    fetchPreviousPage,
    hasNextPage,
    hasPreviousPage,
    isFetchingNextPage,
    isFetchingPreviousPage,
  );
  return (
    <div className={styles.container}>
      {result?.data?.pages[0].posts.map((item) => (
        <PostItem key={item.id} data={item} />
      ))}
    </div>
  );
};

export default News;

이슈

데이터는 불러와지는데 페이지 이동은 어떻게 하는거지..
데이터가 없으니까 된건지 확인할수가없다,,

profile
https://a-honey.tistory.com/
post-custom-banner

0개의 댓글