11. Dependant, Paginated, Infinite Query

Dependant Query : useQuery()의 enabled 옵션 사용

 

만약 쿼리들이 순서대로 실행되어야 할 경우, useQuery()의 enabled 옵션을 사용할 수 있다.

이 값이 true일 때만 쿼리를 실행시키는 옵션이다.

 

만약

const {
  data: projects,
} = useQuery({
  queryKey: ['projects', userId],
  queryFn: getProjectsByUser,
  enabled: !!userId,
});

이렇게 enable 옵션을 설정한다면,

userId가 있다면 true, 없다면 false가 되어 userID의 존재유무에 따라 실행유무를 달리할 수 있다.

예제로 확인해봅시다.

 

Q. currentUsername 값이 있다면, 해당 아이디 값을 이용해 유저의 이름, 프로필 사진 등의 정보를 받아오는 쿼리를 작성하려고 합니다.

 

 const { data: currentUserInfo } = useQuery({
    queryKey : ['userInfo', usernames],
    queryFn : () => getUserInfo(currentUsername), // 유저정보를 불러옴
    enabled : !!currentUsername, // 유저 값이 있을 때만 해당 유저의 데이터를 가져오도록 함
    staleTime: 1000 * 60 * 60, // 매번 refetch 하지않도록 60분 설정
  });

위와 같이 코드를 작성해볼 수 있다.

 

그럼 로그인 전에는 userInfo가 disabled 상태이다가, 로그인을 하게 되면 해당 유저 아이디로 데이터를 잘 캐싱하게된다.

 

const handleLogoutClick = () => {
    queryClient.removeQueries({
      queryKey: ['userInfo', currentUsername],
    });
    setCurrentUsername(undefined);
    navigate('/');
};

만약 로그아웃을 한다면 queryClient.removeQueries를 활용하여

currentUsername을 undefined로 바꿔주고, 기존 userInfo 값을 삭제시키면 된다.

 

Paginated Query : 페이지네이션 구현

 

데이터(게시글)를 한번에 다! 말고 끊어서 불러오고 싶다!

 

const PAGE_LIMIT = 3;

function HomePage() {
  // ...

  const [page, setPage] = useState(0);
  const {
    data: postsData,
    isPending,
    isError,
  } = useQuery({
    queryKey: ['posts', page],
    queryFn: () => getPosts(page, PAGE_LIMIT),
  });

이렇게 page를 설정해두고, getPosts 함수에서도 page에서 limit만큼 불러오게 한다면?

-> 첫 페이지(0)에 해당하는 3개 값만 보인다.

 

이 posts의 데이터를 살펴보면, hasMore이라는 옵션이 있다. 다음 페이지가 있을 때 이걸 true로 보내준다.

 <button
            disabled={!postsData?.hasMore}
            onClick={() => setPage((old) => old + 1)}
          >

따라서 hasMore값에 따라서 다음페이지로 가는 버튼을 비활성화 할 지 말지 결정할 수 있다!

 

페이지네이션에서 부드러운 UI 전환

 

placeholderData라는 옵션이 존재한다. 이 옵션의 값을 keepPreviousData로 설정하면,
페이지가 바뀔 때 새로 pending 상태가 되는 것이 아니라, 이전의 데이터를 보여주다가 fetch가 끝나면 새 데이터로 바꿔 보여주게 된다.

 

<button
        disabled={isPlaceholderData || !postsData?.hasMore}
        onClick={() => setPage((old) => old + 1)}
      >

에러를 방지하기 위해 중간에 업데이트 되는동안은 다음 페이지 버튼을 비활성화 할 필요도 있다.

 

여기서 이제 좀 더 깔끼하게 페이지 로딩을 구현하려면, prefetch하는 방법이 있다.

useEffect(() => {
  if (!isPlaceholderData && postsData?.hasMore) {
    queryClient.prefetchQuery({
      queryKey: ['posts', page + 1],
      queryFn: () => getPosts(page + 1, PAGE_LIMIT),
    });
  }
}, [isPlaceholderData, postsData, queryClient, page]);

뭐 이런식으로 미리 다음페이지 데이터를 fetch해 놓을 수 있기 대문에 좋다.

 

 

근데 내 체감상 시중의 서비스들을 보면, 로딩이 1초이상 걸리는데 로딩중이라는 문구가 뜨지 않는 경우가 많다.

아마 이런식으로 이전의 데이터를 계속 보여주게 설정해서겠지.

근데 그게 썩 좋진 않다. 괜히 렉 걸린 것 같은 느낌이 많이 들어서, 1초 이상(?) 뭐 어느정도 데이터가 많아 처리 시간이 좀 걸린다면 차라리 로딩중이라고 뜨는것도 나쁘지 않을 것 같다.

 

infinite Query : 페이지네이션 대신, 더 불러오기를 구현한다면!

 

먼저 기존의 useQuery()를 useInfiniteQuery()로 바꿔주어야한다.

이는 initialPageParam과 getNextPageParam이라는 옵션을 필수로 가진다.

 

const {
  data: postsData,
  isPending,
  isError,
  fetchNextPage,
} = useInfiniteQuery({
  queryKey: ['posts'],
  queryFn: ({ pageParam }) => getPosts(pageParam, PAGE_LIMIT),
  initialPageParam: 0,
  getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) =>
    lastPage.hasMore ? lastPageParam + 1 : undefined,
});

useQuery()에서의 data는 서버에서 한 페이지만의 정보를 받아와 담고있다면,

useInfiniteQuery()에서는 data.pages에 배열 형태로 모든 페이지 정보를 담고 있다.

 

첫 페이지의 데이터를 받아오면 data.pages 배열의 0번째 인덱스에 그 데이터가 저장되는 것이다.

이렇게 쌓이는 데이터들이 ['posts']라는 쿼리 키로 캐싱된다.

 

initialPageParam 옵션은 초기 페이지 설정 값을 API에 맞게 설정해야 하고,

getNextpageParam 옵션은 다음 페이지의 설정 값을 정하게 된다.

 

파라미터 중

lastPage : 현재까지 가장 마지막 페이지의 데이터, allPages : 모든 페이지의 데이터,
lastPageParam : 가장 마지막 페이지의 설정값, allPageParams : 모든 페이지의 설정값이다.

 

위 파라미터 값들을 통해 다음 페이지 값인 pageParam을 리턴한다.

따라서 hasMore 값을 통해 다음 페이지가 존재한다면, lastPageParam의 값+1을 리턴하도록 한다.

이 값(pageParam)은 queryFn의 파라미터로 전달되어 백에 데이터를 요청하게 되는 것이다!

 

이제 다음 값을 불러오려면, useInfiniteQuery()의 리턴값 중 하나인 fetchNextPage()를 이용하면 된다!

위의 pageParam값이 null, undef가 아니라면 그 값을 쿼리함수의 pageParam으로 전달하여 데이터를 가져온다.
-> onclick()의 함수로 등록해주면 되는 것!!!

 

 

코드잇의 강의를 통해 학습한 내용을 작성하였습니다~!@!@!