개요

Untitled

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

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

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

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

문제

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

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

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