타입스크립트 + useInfiniteQuery : 더보기 기능 구현

728x90
반응형

https://tanstack.com/query/latest/docs/react/guides/query-functions#queryfunctioncontext

 

Query Functions | TanStack Query Docs

A query function can be literally any function that returns a promise. The promise that is returned should either resolve the data or throw an error. All of the following are valid query function configurations: tsx useQuery({ queryKey: ['todos'], queryFn:

tanstack.com

 

1. 타입 지정

[PostList.tsx]

import { QueryFunctionContext, QueryKey, useInfiniteQuery } from '@tanstack/react-query';
import { DocumentData, QueryDocumentSnapshot } from 'firebase/firestore';

interface PostListProps {
  queryKey: QueryKey;
  queryFn: (
    context: QueryFunctionContext<QueryKey, undefined | QueryDocumentSnapshot<DocumentData, DocumentData>>
  ) => Promise<QueryDocumentSnapshot<DocumentData, DocumentData>[]>;
}

 

2. 코드

[PostList.tsx]

function PostList({ queryKey, queryFn }: PostListProps) {
  const { data: posts, fetchNextPage } = useInfiniteQuery({
    queryKey,
    queryFn,
    initialPageParam: undefined as undefined | QueryDocumentSnapshot<DocumentData, DocumentData>,
    getNextPageParam: (lastPage) => {
      if (lastPage.length === 0) {
        return undefined;
      }

      return lastPage[lastPage.length - 1];
    },
    select: (data) => {
      return data.pages.flat().map((doc) => ({ id: doc.id, ...doc.data() } as PostType));
    }
  });

  return (

▶ 외부에서 queryKey와 queryFn을 받아오고 타입은 위에서 interface로 정해놓음

▶ useInfiniteQuery를 사용하여 data와 fetchNextPage 값을 사용 (+ hasNextPage 추가 예정)

 

[initialPageParam]

중요한 부분 : tanstack  v5는 initialPageParam이 새로 생김.

=> initialPageParam에 지정한 타입이 getNextPageParam의 return 값의 타입

=> 처음에 데이터를 보여주기 위해서 undefined가 필요하여 falsy value를 넣어줌

=> undefined 는 as 를 사용해서 강제주입하는데 undefined값 이거나~ QueryDocument~이거나 (파이어베이스)

 

[getNextPageParam]

▶ input인 lastPage와 output인 return값은 initialPageParam의 타입에 집중해야함.

 

[select]

▶ 옵션인데 데이터를 가공해서 사용가능

▶ select : (data) => Data 안에 pages / pageParams가 있는데 Data를 가르킴

▶ return data.pages => 위에서 말한 Data 안에 pages에 접근

▶ flat() 메소드 : 배열안 객체를 (이중배열) 를 단일배열로 만들기 위함

ex. arr =[1,2,[3,4]] => arr.flat(); // [1,2,3,4]

ex. arr.flat(2); // 2 = 없애는 대괄호 개수

ex. arr.flat(Infinity);   // 모두 없애기

▶ map 메소드 : queryFn에서 사용한 데이터를 여기서 가공

 

 

[pageListApi.ts] > [queryFn = getAdminPostList]

import { QueryFunctionContext, QueryKey } from '@tanstack/react-query';
import { db } from '../shared/firebase';
import { DocumentData, QueryDocumentSnapshot, collection,getDocs,limit,query,startAfter,where} from 'firebase/firestore';
import { Category } from '../components/viewAll/ViewAllBody-jy-2';

//관리자 
export const getAdminPostList = async (pageParam: undefined | QueryDocumentSnapshot<DocumentData, DocumentData>) => {

  const q = pageParam
    ? query(collection(db, 'test'), where('role', '==', 'admin'), startAfter(pageParam), limit(4))
    : query(collection(db, 'test'), where('role', '==', 'admin'), limit(4));

  const querySnapShot = await getDocs(q);
 
  return querySnapShot.docs;
  // return querySnapShot.docs.map((doc) => ({
  //   id: doc.id,
  //   ...(doc.data() as Omit<PostType, 'id'>) //id 제외하고 나머지 필드를 PostType으로 변환
  // }));
};

▶ 매개변수 pageParam의 타입은 initialPageParam과 동일

▶ pageParam이 값이 있으면, test 콜렉션의 role이 admin값만 4개 출력 ★startAfter(pageParam) 

    pageParam이 값이 없으면,(undefined) : 처음 4개의 게시물 보여주기

▶ 처음에, return 값을 여기서 가공해서 반환했는데, querySnapShot.docs로 반환 후

useInfiniteQuery의 Select 부분에서 가공

 

 

▶(파이어베이스) 스냅샷 처럼 사용 : 이것도 사용하려고 했었음

=> 그러다가 startAfter에 lastVisble이 아닌 pageParam으로 대체 

const documentSnapshots = await getDocs(first);

// Get the last visible document
const lastVisible = documentSnapshots.docs[documentSnapshots.docs.length-1];

 

 

쿼리 커서로 데이터 페이지화  |  Firestore  |  Firebase

Firebase 데모 데이가 시작되었습니다. Google 최고의 기술을 활용하여 AI 기반 풀 스택 앱을 빌드하고 성장시키는 방법에 관한 데모를 시청하세요. 의견 보내기 쿼리 커서로 데이터 페이지화 컬렉션

firebase.google.com

 

 

[PostList.tsx]

 return (
    <St.MainSubWrapper>
      <St.ContentsWrapper>
        <St.Contents>
          {posts?.map((post) => (
            <St.Content key={post.id}>
              <p>{post.title}</p>
              <p>{post.content}</p>
              <p>{post.category}</p>
            </St.Content>
          ))}
        </St.Contents>
      </St.ContentsWrapper>
      <St.MoreContentWrapper>
        <button onClick={() => fetchNextPage()}>더보기...</button>
      </St.MoreContentWrapper>
    </St.MainSubWrapper>
  );

▶ 화면

▶ useInfiniteQuery에서 data: posts => 사용

▶ 버튼 클릭시 : () => fetchNextPage() 를 사용해서 queryFn 실행

 

 

3. useInfiniteQuery 실행 순서

1) queryFn 실행

2) getNextPageParam 실행 -> 리턴 값은 hook메모리에 저장

3) undefined에서 값이 들어오면서 hasNextPage:true로 변경

4) 그리고,, 이벤트리스너에 의해 fetchNextPage를 사용하면 => 다시 query 실행

5) 이때 getNextPageParam의 리턴값을 queryFn의 매개변수로 넘겨줌

 

 

끝.

반응형