무한스크롤 구현하기

Ahyeon, Jung·2023년 12월 4일

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

사용자가 눈으로 보고 있는 화면의 높이(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/

0개의 댓글