페이지네이션 쿼리

모찌모찌·2024년 5월 11일

리액트쿼리

목록 보기
5/7

기존에는 모든 포스트를 다 한 번에 불러왔지만 이번에는 세 개씩 끊어서 불러오고, 다음 페이지 버튼을 누르면 그다음 데이터를 세 개씩 받아 오도록 하려고 합니다.

먼저 현재 포스트를 받아오는 API 함수를 page와 limit 값을 받도록 변경해 주겠습니다. 참고로 백엔드 API에 쿼리 파라미터로 page와 limit를 넘겨주면 해당하는 page의 데이터를 limit 개수만큼 보내주도록 설계가 되어있어요.

// api.js

export async function getPosts(page = 0, limit = 10) {
  const response = await fetch(`${BASE_URL}/posts?page=${page}&limit=${limit}`);
  return await response.json();
}

1. page 별로 데이터 캐싱

const [page, setPage] = useState(0);
const PAGE_LIMIT = 3;

const {
    data: postsData,
    isPending,
    isError,
  } = useQuery({
    queryKey: ['posts', page],
    queryFn: () => getPosts(page, PAGE_LIMIT),
  });
  
  const posts = postsData?.results ?? [];

✅ 결과

  • result 3 items : 데이터가 3개씩 보이게 된다.
  • currentPage : 현재 페이지
  • hasMore : 다음페이지가 있으면 true, 없으면 false
  • count : 총 아이템 개수

✅ hasMore 값으로 페이지 버튼 활성/비활성 결정

	<button
        disabled={!postsData?.hasMore}
        onClick={() => setPage((old) => old + 1)}
    >

다음 페이지로 넘어갈 때마다 아래와 같이 매번 로딩 메시지가 뜨는데요. 새로운 페이지에 해당하는 쿼리를 보낼 때마다 완전히 새로운 쿼리로 인식을 하기 때문에 계속 pending 상태가 되기 때문입니다.

2. placeholderData

리액트 쿼리에서는 좀 더 부드러운 UI 전환을 위해 placeholderData 라는 것을 설정해 줄 수 있습니다.

useQuery()에서 placeholderData 옵션에 keepPreviousData 혹은 (prevData) => prevData를 넣어주면 페이지가 새로 바뀌더라도 매번 pending 상태가 되지 않고, 이전의 데이터를 유지해서 보여주다가 새로운 데이터 fetch가 완료되면 자연스럽게 새로운 데이터로 바꿔서 보여주게 됩니다.

import { keepPreviousData } from '@tanstack/react-query';

const {
  data: postsData,
  isPending,
  isError,
} = useQuery({
    queryKey: ['posts', page],
    queryFn: () => getPosts(page, PAGE_LIMIT),
    placeholderData: keepPreviousData, //이 부분!!
});
  1. 다음 버튼을 누르면 fetching 상태이지만 로딩 메시지가 뜨지 않고 이전 페이지의 내용이 그대로 보입니다.
  2. 그러다가 데이터를 모두 받아 오면 다음 페이지의 내용으로 바뀝니다.

3. isPlaceholderData

중간 과정에서 다음 페이지 버튼이 활성화된 채로 있는데요. 현재 보이는 데이터가 이전 데이터, 즉 placeholderData라면 다음 페이지 버튼을 비활성화해 주도록 합시다. 그렇지 않으면 유저가 다음 페이지 버튼을 마구 누르는 경우, 존재하지 않는 페이지로 리퀘스트가 갈 수도 있기 때문이에요. useQuery()의 리턴 값에서 isPlaceholderData 값을 활용하면 됩니다.

const {
  data: postsData,
  isPending,
  isError,
  isPlaceholderData,
} = useQuery({
  queryKey: ['posts', page],
  queryFn: () => getPosts(page, PAGE_LIMIT),
  placeholderData: keepPreviousData,
});

return (
  ...
      <button
        disabled={isPlaceholderData || !postsData?.hasMore}
        onClick={() => setPage((old) => old + 1)}
      >
        &gt;
      </button>
);

4. prefetch

현재 내가 보고 있는 페이지가 1 페이지여도 백그라운드에서 2 페이지 데이터를 미리 fetch해 놓기 때문에 다음 페이지로 갈 때 전혀 어색함이나 끊김이 없이 2 페이지의 데이터를 보여줄 수 있습니다.

...

useEffect(() => {
  if (!isPlaceholderData && postsData?.hasMore) {
    queryClient.prefetchQuery({
      queryKey: ['posts', page + 1],
      queryFn: () => getPosts(page + 1, PAGE_LIMIT),
    });
  }
}, [isPlaceholderData, postsData, queryClient, page]);
...

profile
꼬꼬마 개발자 지망생

0개의 댓글