개요
게시글이 늘어남에 따라 게시글들이 창을 뚫고 넘어가는 지경에 이르렀기 때문에 페이지네이션을 구현하기로 결심했다.
해당 문서를 참고해서 작성을 했는데, 시점 설정이 어려워서 오랜 시간 동안 애먹었다.
될 듯 안 될듯 했기에 스트레스가 정말 극심했다…
문제
// pagenation
const temp = async () => {
const qu = query(
collection(dbService, "memos"),
orderBy("createdAt", "desc"),
limit(5),
);
const documentSnapshots = await getDocs(qu);
const lastVisible = documentSnapshots.docs[documentSnapshots.docs.length - 1];
const next = query(
collection(dbService, "memos"),
orderBy("createdAt", "desc"),
startAfter(lastVisible),
limit(5),
);
onSnapshot(next, (snapshot) => {
const memoArr = snapshot.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}));
setMemos(memoArr);
});
};
공식 문서를 보고 작성한 페이지네이션.
파이어베이스는 startAfter, startAt 같은 옵션이 있는데, 인덱스를 받는 게 아닌, 현재 페이지의 마지막 데이터를 받아오면 그 다음 페이지를 작성해주는 기능이다.
그러나 이런 식으로 작성했을 때, 첫 페이지 시점 기준으로만 움직였기에 바로 다음 페이지까지밖에 보여주지 않는 문제가 있었다.
백엔드 서비스를 많이 다뤄본 적이 없어, 문서에서 지시하는 것 이외에는 어떤 방법으로 이 문제를 타계해 나가야 할지 막막했다.
방법 1.
const [current, setCurrent] = useState();
const documentSnapshots = await getDocs(current);
const lastVisible = documentSnapshots.docs[documentSnapshots.docs.length - 1];
const next = query(
collection(dbService, "memos"),
orderBy("createdAt", "desc"),
startAfter(lastVisible),
limit(5),
);
setCurrent(next);
현재 페이지를 변수에 밀어넣고, 이 변수를 가져와서 쓰면 되지 않을까 해서 해 본 방법.
안된다 … 그럼 변수에 뭘 저장해둬야하는지 ….
실패
방법 2.
// 키를 저장할 공간을 마련한다.
const [key, setKey] = useState<QueryDocumentSnapshot<DocumentData>>();
// 첫번째 페이지 요청
const start = query(
collection(dbService, "memos"),
orderBy("createdAt", "desc"),
limit(5),
);
useEffect(() => {
// 첫번째 페이지에 들어갈 데이터를 만든다.
onSnapshot(start, (snapshot) => {
const memoArr = snapshot.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}));
// 집어넣는다.
setMemos(memoArr);
// 키에 문서 데이터 마지막 요소를 집어넣는다***
setKey(snapshot.docs[snapshot.docs.length - 1]);
});
}, []);
// 페이지를 새로 요청하는 함수
const morePage = async () => {
// 다음 페이지 요청
const queryRef = query(
collection(dbService, "memos"),
orderBy("createdAt", "desc"),
// key값을 넣어서 요청한다***
startAfter(key),
limit(5),
);
// 만든 페이지 요청을 문서로 받아온다.
const snap = await getDocs(queryRef);
// 데이터 공백 여부를 확인하고 현재 페이지의 마지막 키를 재설정한다***
snap.empty ? setNoMore(true) : setKey(snap.docs[snap.docs.length - 1]);
// 다음 페이지에 들어갈 데이터를 만든다.
onSnapshot(queryRef, (snapshot) => {
const memoArr = snapshot.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}));
// 집어넣는다.
setMemos(memoArr);
});
};
주석에 *** 해 둔 부분이 페이지네이션 구성의 핵심 요소.
애초에, 첫 페이지를 구성할 때, 마지막 항목을 key에 저장.
다음 페이지 요청이 들어오면, 쿼리를 새로 요청하면서 그 키를 startAfter에 집어넣는다.
(옵션) 데이터 존재 여부 판별 후, 현재 페이지의 마지막 데이터를 키에 다시 집어넣는다.
그럼 정상적으로 다음페이지로 넘어간다.