그럼에도 불구하고

👨‍💻

[React Query] 페이지네이션(Pagination)과 데이터 프리페칭(Prefetching) 본문

React/React query

[React Query] 페이지네이션(Pagination)과 데이터 프리페칭(Prefetching)

zenghyun 2023. 10. 18. 22:11

 

React Query에서 페이지네이션(Pagination)과 데이터 프리페칭(Prefetching)에 대해 알아보겠습니다.

 

 

🧑🏻‍💻 Pagination

예를 들어, 블로그의 게시글을 나열하는데 페이지 번호를 매겨서 나타낼 때가 있습니다. 이때 React Query의 Pagination을 사용하면 현재 페이지(current page) 상태를 통해, 현재 페이지를 파악할 수 있습니다.

📌 사용 예시

  • 최대 페이지의 수는 10  const MAX_POST_PAGE = 10;
  • 사용자가 다음, 또는 이전 페이지로 가는 버튼을 누르면 해당 페이지로 이동하며 currentPage의 상태를 업데이트합니다.
  • React Query는 바뀐 쿼리 키를 감지하고 새로운 쿼리를 실행해서 새 페이지가 표시됩니다.

👉 전체 코드 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import { useEffect, useState } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
 
import { PostDetail } from "./PostDetail";
const MAX_POST_PAGE = 10;
 
async function fetchPosts(currentPage) {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/posts?_limit=10&_page=${currentPage}`
  );
  return response.json();
}
 
export function Posts() {
  const [currentPage, setCurrentPage] = useState(1);
  const [selectedPost, setSelectedPost] = useState(null);
 
  const quertClient = useQueryClient();
 
  useEffect(() => {
    if (currentPage < MAX_POST_PAGE) {
      const nextPage = currentPage + 1;
      quertClient.prefetchQuery(["posts", nextPage], () =>
        fetchPosts(nextPage)
      );
    }
  }, [currentPage, quertClient]);
 
  const { data, isError, error, isLoading } = useQuery({
    queryKey: ["posts", currentPage],
    queryFn: () => fetchPosts(currentPage),
    staleTime: 2000,
    keepPreviousData: true,
    /**
     * 쿼리 키가 바뀌어도 지난 데이터를 유지해서 혹여나 이전 페이지로 돌아갔을 때
     * 캐시에 해당 데이터가 있도록 만들기 위해 keepPreviousData를 true로 설정
     */
  });
 
  if (isLoading) return <h3>Loading...</h3>;
  if (isError)
    return (
      <>
        <h3>Oops, something went wrong!</h3>
        <p>{error.toString()}</p>
      </>
    );
 
  return (
    <>
      <ul>
        {data.map((post) => (
          <li
            key={post.id}
            className="post-title"
            onClick={() => setSelectedPost(post)}
          >
            {post.title}
          </li>
        ))}
      </ul>
      <div className="pages">
        <button
          disabled={currentPage <= 1}
          onClick={() => {
            setCurrentPage((previousPage) => previousPage - 1);
          }}
        >
          Previous page
        </button>
        <span>Page {currentPage}</span>
        <button
          disabled={currentPage >= MAX_POST_PAGE}
          onClick={() => {
            setCurrentPage((previousPage) => previousPage + 1);
          }}
        >
          Next page
        </button>
      </div>
      <hr />
      {selectedPost && <PostDetail post={selectedPost} />}
    </>
  );
}
 
cs

 

1
2
3
4
 const { data, isError, error, isLoading } = useQuery({
    queryKey: ["posts", currentPage],
    queryFn: () => fetchPosts(currentPage),
  });
cs

 페이지마다 다른 쿼리 키가 필요하기 때문에 쿼리 키를 배열로 업데이트하여 가져오는 페이지 번호를 다음과 같이 포함합니다.

queryKey: ["posts", currentPage] 

 

1
2
3
4
5
6
const { data, isError, error, isLoading } = useQuery({
    queryKey: ["posts", currentPage],
    queryFn: () => fetchPosts(currentPage),
  });
 
 
cs

위와 같이 쿼리 키에 currentPage를 포함하게 되면 React Query가 바뀐 쿼리 키를 감지하여 새 쿼리 키에 대한 데이터를 업데이트합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
     <button
          disabled={currentPage <= 1}
          onClick={() => {
            setCurrentPage((previousPage) => previousPage - 1);
          }}
        >
          Previous page
        </button>
        <span>Page {currentPage}</span>
        <button
          disabled={currentPage >= MAX_POST_PAGE}
          onClick={() => {
            setCurrentPage((previousPage) => previousPage + 1);
          }}
        >
          Next page
      </button>
 
async function fetchPosts(currentPage) {
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/posts?_limit=10&_page=${currentPage}`
  );
  return response.json();
}
 
const [currentPage, setCurrentPage] = useState(1);
 
 
cs

양쪽 페이지 버튼을 눌러 페이지를 이동하게 되는데, queryKey의 currentPage의 변경되면 queryFn도 바뀌게 됩니다.

즉, fetchPost에 currentPage를 인자로 받아 해당 페이지를 가져오게 됩니다

 

 

 

🧑🏻‍💻 Prefetching ( 데이터 미리 가져오기 )

위의 영상처럼 페이지전환은 잘 되지만 페이지를 가져올 때마다 바뀐 queryKey로 인해 새롭게 데이터를 받아오는 과정에서

Loading... 이 나타나는 것을 볼 수 있습니다. 

 

이는 UX (User Experience) 측면에서 좋지 않습니다. ( 의도한 경우가 아니라면 )

 

Loading... 텍스트가 나오는 이유는 다음 페이지에 대한 캐시가 없기 때문입니다.

 

여기서 Prefetching을 사용하게 되면 이 문제를 해결할 수 있습니다. 

📌 Prefetching이란?

프리패칭은 데이터를 캐시에 추가하여, 구성할 수 있으며 기본값은 stale 상태입니다. 

 

즉, 데이터를 사용하고자 할 때 stale state에서 데이터를 다시 가져오며, 데이터를 가져오는 중에는 캐시에 있는 데이터를 이용해 화면에 나타내게 됩니다. ( cacheTime이 만료되지 않았다는 가정하에 )

 

prefetching 기능을 사용하기 위해서 queryClient를 선언하겠습니다. ( prefetch는 queryClient의 메서드입니다. :) )

 

🏷️ Ref

https://tanstack.com/query/latest/docs/react/reference/QueryClient#queryclientprefetchquery

 

QueryClient | TanStack Query Docs

QueryClient The QueryClient can be used to interact with a cache:

tanstack.com

 

import { useQuery, useQueryClient } from "@tanstack/react-query";

... 

const quertClient = useQueryClient();

 

여기서 useEffect를 사용하여 현제 페이지에 생기는 변경 사항을 활용하는 방식을 사용하겠습니다.

 

⭐️ 버튼을 눌렀을 때 onClick을 이용하여 prefetching 하면 안 될까?

onClick 버튼을 눌렀을 때 prefetchQuery를 사용하지 않는 이유는 상태 업데이트가 비동기식으로 일어나기 때문에 이미 업데이트가 진행됐는지 알 방법이 없고, 현재 페이지가 어디인지 알 수 있는 확실한 방법이 없기 때문입니다.

 

그에 반해, useEffect 사용하면 [] 사용하여 currentPage 변경되거나 mount 때마다 prefetchQuery 사용하여 원활하게 데이터를 캐시에 추가할 있습니다.

 

1
2
3
4
5
6
7
8
useEffect(() => {
    if (currentPage < MAX_POST_PAGE) {
      const nextPage = currentPage + 1;
      quertClient.prefetchQuery(["posts", nextPage], () =>
        fetchPosts(nextPage)
      );
    }
  }, [currentPage, quertClient]);
cs

📌 keepPreviousData

useQuery에서 keepPreviousData를 사용하여 쿼리 키가 바뀔 때도 지난 데이터를 유지할 수 있습니다.

혹여나 이전 페이지로 돌아갔을 때 캐시에 해당 데이터가 있도록 만들기 위하여 사용합니다. 

 

1
2
3
4
5
6
 const { data, isError, error, isLoading } = useQuery({
    queryKey: ["posts", currentPage],
    queryFn: () => fetchPosts(currentPage),
    staleTime: 2000,
    keepPreviousData: true,
  });
cs

 

 

 

위와 같이 다음 페이지를 prefetch하여 cache에 존재하는 데이터를 이용해 다음 페이지를 가져오는 것을 확인할 수 있습니다. :)

 

👉 Ref

https://tanstack.com/query/latest/docs/react/reference/QueryClient#queryclientprefetchquery

 

 

Comments