오늘은 페이지네이션에 대해 공부했다!
페이지네이션을 구현하기 위해서 Supabase에 더미데이터를 넣고 연습했다!
const getPosts = async(page=0, limit=10) => {
return await fetch(`${BASE_URL}/posts?page=${page}&limit=${limit}`)
}
일반적으로 페이지네이션을 구현할 때는 url에 query parameter로 page와 limit 값을 전달하는 것이 일반적이다. 다만 이번에는 백엔드가 아니라 Supabase DB로 요청을 보내기 때문에 코드의 형태가 조금 다르다!
// supabase.js
import { createClient } from "@supabase/supabase-js";
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL;
const supabaseKey = import.meta.env.VITE_SUPABASE_KEY;
export const supabase = createClient(supabaseUrl, supabaseKey);
먼저 supabse client를 먼저 export 해주고!
supabase에서는 이렇게 pagination을 위한 함수를 제공하고 있다!
// api.posts.js
/**
*
* @param {number} page 현재 페이지.
* @param {number} limit 한 페이지에 보여줄 아이템 수.
* @returns
*/
export const getPagenatedUsers = async (page, limit) => {
const { data, error } = await supabase
.from("users")
.select("*")
.range(page * limit, (page + 1) * limit - 1);
return { data, error };
};
range()
에 page
와 limit
을 잘 넣어주고
// App.jsx
function App() {
const [page, setPage] = useState(0);
const LIMIT = 5;
const {
data: users,
error,
isPending,
} = useQuery({
queryKey: ["users", page],
queryFn: () => getPagenatedUsers(page, LIMIT),
});
이렇게 useQuery
를 사용할 때, queryKey
의 dependency array에 변수까지 넣어주면 완성입니다!
<button onClick={() => setPage((prev) => prev + 1)}>
다음 페이지
</button>
supabase를 활용해서 만들면서 한 가지 불편했던 점!
보통의 경우에는 pagination
과 관련된 데이터를 요청할 때, hasMore
로 대표되는 속성을 response
에 같이 넣어서 보내준다.
예를 들어서, 내가 20페이지에 대한 요청을 보냈는데 만약 21페이지가 존재하지 않는다면 response
의 안에 20페이지에 대한 정보를 보내줄 때 hasMore
나 nextPage
같은 속성에 false
가 할당되서 돌아온다.
const getPosts = async(page=0, limit=10) => {
return await fetch(`www.my-web-page/posts?page=20&limit=10`)
}
// response
{
data : [...], // 20페이지에 대한 데이터
hasMore : false, // hasMore가 false면 다음 페이지가 없다는 응답.
}
그런데 supabase의 pagination은 다음 페이지의 존재 유무와 관련된 정보를 response에서 제공하지 않는다!
그래서 component 내부에 hasMore
라는 state
를 만들어서 직접 구현해보기로 했다.
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useEffect, useState } from "react";
import { getPagenatedUsers } from "./api.posts";
// App.jsx
function App() {
const queryClient = useQueryClient();
const [page, setPage] = useState(0);
const [hasMore, setHasMore] = useState(true);
const LIMIT = 5;
const {
data: users,
error,
isPending,
} = useQuery({
queryKey: ["users", page],
queryFn: () => getPagenatedUsers(page, LIMIT),
});
useEffect(() => {
queryClient.prefetchQuery({
queryKey: ["users", page + 1],
queryFn: async () => {
const response = await getPagenatedUsers(page + 1, LIMIT);
if (response.data.length === 0) {
setHasMore(false);
} else {
setHasMore(true);
}
return response;
},
});
}, [page]);
useEffect
안에서 queryClient.prefetchQuery()
를 활용해서 다음 페이지의 데이터를 미리 받아오는데, 만약 response.data
의 길이가 0이라면 hasMore
를 false
로 바꾼다.
<button
disabled={page === 0}
onClick={() => setPage((prev) => prev - 1)}
>
이전 페이지
</button>
<button disabled={!hasMore} onClick={() => setPage((prev) => prev + 1)}>
다음 페이지
</button>
이렇게 page
가 0일 때, hasMore
가 false
일 때 각각 버튼을 비활성화시켜서 존재하지 않는 페이지로 요청을 보내는 것을 막았다.