개요

게시글이 늘어남에 따라 게시글들이 창을 뚫고 넘어가는 지경에 이르렀기 때문에 페이지네이션을 구현하기로 결심했다.

쿼리 커서로 데이터 페이지화

해당 문서를 참고해서 작성을 했는데, 시점 설정이 어려워서 오랜 시간 동안 애먹었다.

될 듯 안 될듯 했기에 스트레스가 정말 극심했다…

문제

// 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에 집어넣는다.

(옵션) 데이터 존재 여부 판별 후, 현재 페이지의 마지막 데이터를 키에 다시 집어넣는다.

그럼 정상적으로 다음페이지로 넘어간다.