React Query로 무한 스크롤 구현하기

김채은·2022년 6월 7일
post-thumbnail

SW마에스트로에서 리액트 심화 스터디를 열게 됐다. 리액트에 대한 공부를 심도있게 하고 싶은 사람들을 모아서 함께 공부와 코드리뷰, 네트워킹을 진행하기로 했다. 첫 번째 프로젝트로는 쇼핑몰 만들기를 진행한다.

🐇 구현 목표

상품 목록

  • Unsplash API를 요청해서 그리드로 상품 목록을 표시한다.
    • 상품 목록은 무한스크롤로 구현한다.
  • 상품을 클릭했을 때 상품 상세페이지로 넘어간다.
  • 상품 금액은 likes * 1000으로 한다.

🐇 Unsplash API

Pagination이 돼있는 오픈 API를 찾던 중 스터디원이 추천한 API이다. 사진 리스트를 쭉 불러올 수 있어서 쇼핑몰 화면을 구성하는 데에 적합하다고 생각됐다.

사진을 제품으로 생각하고, 금액은 likes 수에 1000을 곱한 값으로 책정했다. 아쉬운 점은 사진에 제목이 따로 없었다는 점이다. 그래서 제품 이름 칸에는 작가의 이름을 넣었다.

Unsplash API Documentation | Free HD Photo API | Unsplash

Authorization

회원가입 후 Client ID를 얻어서 헤더의 Authorization에 넣어주고, baseURL을 설정해서 Axios 인스턴스를 생성했다.

import axios from "axios";

export const api = axios.create({
  baseURL: "https://api.unsplash.com",
  headers: {
    Authorization: `Client-ID {client ID}`,
  },
});

Interface

사진의 배열로 많은 데이터가 들어오는데, 그 중 내가 사용할 데이터만 타입을 입력해주었다.

interface IPhoto {
  id: string;
  likes: number;
  urls: {
    thumb: string;
  };
  user: {
    first_name: string;
  };
}

🐇 useInfiniteQuery

useInfiniteQuery 사용법

useInfiniteQuery 는 모든 데이터를 한번에 불러오지 않고, pagination을 통해 서버에서 정해둔 일정 양만큼의 데이터를 순차적으로 불러올 수 있는 React Query의 Hook이다.

첫 번째 인자로는 useQuery 와 마찬가지로 고유 Key를 넣어주고, 두 번째로는 데이터 패치 함수를 넣어준다. 세 번째 인자는 Option을 넣어주는 부분인데 밑에서 설명할 것이다.

const { data, hasNextPage, fetchNextPage, isFetchingNextPage } =
    useInfiniteQuery(
      ["photos"],
      async ({ pageParam = 1 }) => {
        return await fetchPhotos(pageParam);
      },
      {
        // Options
      }
    );

데이터 패치해서 보여주기

fetchPhotos 를 보면 다음과 같다. pageParams로 지정된 페이지를 요청하고, data와 nextPage 를 리턴한다. nextPage 에는 기존 페이지에 1을 더한 값을 보내준다.

import { api } from "../utils/myApi";

export const fetchPhotos = async (pageParams: number) => {
  const { data } = await api.get(`/photos?page=${pageParams}`);
  return {
    data,
    nextPage: pageParams + 1,
  };
};

이렇게 리턴된 데이터를 console에 찍어보았다.

pages 배열의 0번 인덱스에 data와 nextPage가 들어있다. 다음 페이지가 들어오면 pages 배열에 같은 형식의 Object가 입력되는 것으로 예측된다.

따라서 pages를 map으로 돌고, 그 안에서 page의 data를 map으로 돌아서 데이터를 그리드 형식으로 보여주도록 컴포넌트를 구성했다.

<section>
  {data?.pages.map((page, i) => {
    return (
      <React.Fragment key={i}>
        {page?.data.map((photo: IPhoto) => (
          <div key={photo.id}>
            {/* 제품 정보 Component */}
          </div>
        ))}
      </React.Fragment>
    );
  })}
</section>

다음 페이지 데이터 패치하기

아래는 다음 데이터를 패치하는 버튼을 넣어주었다.

  • isFetchingNextPage : 다음 페이지를 패치하는 중인지
  • hasNextPage : 다음 페이지가 있는지

다음 페이지가 존재하고 패치 중이지 않으면 버튼이 활성화된다. 버튼을 클릭하면 fetchNextPage를 호출한다.

<button
  type="button"
  onClick={() => fetchNextPage()}
  disabled={!hasNextPage || isFetchingNextPage}
>
  {isFetchingNextPage
    ? "Loading more..."
    : hasNextPage
    ? "Load More"
    : "Nothing more to load"}
</button>

여기서 세 번째 인자인 Options가 등장한다. fetchNextPage 를 호출하면 getNextPageParam 에서 다음 페이지의 번호를 가져오게 된다. lastPage에는 이전에 불러왔던 페이지 정보, 즉 data.pages[0]이 들어있다. 따라서 여기서의 리턴값은 2가 된다.

{
  getNextPageParam: (lastPage) => {
    return lastPage.nextPage;
  },
}

버튼을 누르고 data를 console에 찍어보면, 예측대로 다음 페이지가 pages 배열에 추가된 것을 확인할 수 있다.

🐇 완성 화면

Load More 버튼을 누르면 다음 페이지 데이터까지 화면에 표시된다.

🐇 Develop 해볼 부분

  • data를 찍었을 때 pages와 함께 등장하는 pageParams 라는 배열이 어디서 나온 것이고 어떻게 동작하는지에 대해 더 알아보고 싶다.
  • IntersectionObserver 를 통해 페이지의 최하단에 도달했을 때 자동으로 다음 데이터를 패치하는 것을 구현하고 싶다.
profile
배워서 남주는 개발자 김채은입니다 ( •̀ .̫ •́ )✧

4개의 댓글

comment-user-thumbnail
2022년 6월 13일

안녕하세요. 파도타다 넘어오게 되었는데, 좋은 글 잘보고 갑니다.
intersection observer 관련해 현재 프로젝트를 진행하고 있는데 혹시 참고가 되실까 링크 남겨드려요

https://github.com/depromeet/ygtang-client/blob/main/src/hooks/common/useIntersectionObserver/useIntersectionObserver.ts
https://github.com/depromeet/ygtang-client/blob/main/src/pages/index.tsx
https://github.com/depromeet/ygtang-client/blob/main/src/hooks/api/inspiration/useGetInspirationListWIthInfinite.ts

소마 13기 화이팅입니다 :D

1개의 답글
comment-user-thumbnail
2022년 10월 10일

소스 코딩 있나요??

1개의 답글