useInfiniteQuery부분은 전 게시물에서 다룬 내용이다.
useInfiniteQuery와 firebase를 가지고 무한 스크롤을 구현할 것이기 때문에 다시 기재한다.
useInfiniteQuery
const {
data, // data.pages를 갖고 있는 배열
error, // error 객체
fetchNextPage, // 다음 페이지를 불러오는 함수
fetchPreviousPage, // 이전 페이지를 불러오는 함수
hasNextPage, // 다음 페이지가 있는지 여부, Boolean
hasPreviousPage, // 이전 페이지 여부, Boolean
isFetchingNextPage, // 추가 페이지 fetching 여부, Boolean
isFetchingPreviousPage, // 이전 페이지 fetching 여부, Boolean
status, // loading, error, success 중 하나의 상태, string
...result
} = useInfiniteQuery(queryKey, ({ pageParam = 1 }) => fetchPage(pageParam), {
...options,
getNextPageParam: (lastPage, allPages) => lastPage.nextCursor,
getPreviousPageParam: (firstPage, allPages) => firstPage.prevCursor,
})
위 코드는 공식문서에 나와있는 코드에 내 입맛을 추가한 코드다.
공식문서의 설명은 살짝 불친절한 감이 있어서 실제 코드를 보면서 알아보자.
const {
data,
fetchNextPage,
status,
} = useInfiniteQuery(
'infiniteData',
getData,
{
getNextPageParam: () => {
return true;
},
}
);
내가 가져올 데이터는 페이지 파라미터가 없다. 그래서 리턴값으로 true를 주었다.
물론 getNextPageParam부분이 없으면 다음페이지를 가져오지 않는다.
useBottomScrollListener(() => {
fetchNextPage();
});
useBottomScrollListener라는 라이브러리를 사용해서 스크롤이 바닥에 닿을 때 fetchNextPage()
함수를 실행시킨다.
fetchNextPage()
함수는 useInfiniteQuery의 다음페이지를 불러오는 함수다.
firebase
데이터는 파이어베이스를 사용해서 가져오는데,
파이어베이스는 쿼리커서로 페이지를 나눠주는 기능을 아래처럼 제공한다.
import { collection, query, orderBy, startAfter, limit, getDocs } from "firebase/firestore";
// Query the first page of docs
const first = query(collection(db, "cities"), orderBy("population"), limit(25));
const documentSnapshots = await getDocs(first);
// Get the last visible document
const lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1];
console.log("last", lastVisible);
// Construct a new query starting at this document,
// get the next 25 cities.
const next = query(collection(db, "cities"),
orderBy("population"),
startAfter(lastVisible),
limit(25));
위 코드가 쿼리커서로 페이지를 나누는 코드다.
startAt() 또는 startAfter()메서드를 사용하여 쿼리의 시작점을 정의한다.
startAt()메서드는 시작점을 포함하고, startAfter() 메서드는 시작점을 제외한다.
예를 들어 쿼리에 startAt(A)을 사용하면 전체 알파벳이 반환되고, startAfter(A)를 대신 사용하면. B-Z가 반환된다.
위 코드를 설명하면, 처음엔 25개의 데이터만 불러온다.
그 후로 startAfter(lastVisible)을 지정해주고, 다음부터 lastVisible를 넣은 25개의 데이터를 불러온다.
lastVisible를 넣었기 때문에 lastVisible에 지정된 docs를 제외한 이후의 25개의 데이터를 불러온다.
위 공식문서 코드를, 조건문을 사용해서 현제 프로젝트에 맞게 변형한 코드가 아래 코드다.
let lastVisible: any = undefined;
export const getData = async () => {
const getData: any = [];
let q;
if (lastVisible === -1) {
return;
} else if (lastVisible) {
q = query(
collection(dbService, 'test'),
orderBy('createdAt', 'desc'),
limit(4),
startAfter(lastVisible)
);
} else {
q = query(
collection(dbService, 'test'),
orderBy('createdAt', 'desc'),
limit(16)
);
}
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
getData.push({ id: doc.id, ...doc.data() });
if (querySnapshot.docs.length === 0) {
lastVisible = -1;
} else {
lastVisible = querySnapshot.docs[querySnapshot.docs.length - 1];
}
});
return getData;
};
더이상 불러올 데이터가 없는 경우 lastVisible를 -1로 정해주는 것으로, 함수가 실행되지 않게 할 수 있다.
개인적으로 타입에 any가 있으면 찝찝하기 때문에 이제 any를 없애러 가야겠다.