일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 코딩테스트
- 자바문제풀이
- github
- 자바
- redux
- react
- JavaScript
- 변수
- media query
- TypeScript
- cleancode
- @media
- java
- node.js
- CSS
- 코드업
- webpack
- git
- node
- 그럼에도 불구하고
- HTML
- 그럼에도불구하고
- 반응형 페이지
- Servlet
- coding
- react-router-dom
- max-width
- JS
- 프론트엔드
- frontend
- Today
- Total
그럼에도 불구하고
[React Query] 페이지네이션(Pagination)과 데이터 프리페칭(Prefetching) 본문
[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
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
'React > React query' 카테고리의 다른 글
[React Query] isLoading과 isFetching은 헷갈려 (0) | 2023.10.19 |
---|---|
[React Query] React Query를 TypeScript로 사용할 때 Type 지정 (0) | 2023.10.14 |
[React Query] useInfiniteQuery와 Infinite Scroll (0) | 2023.08.30 |
[React Query] React Query에 대하여 (0) | 2023.08.24 |