[React Query] Section5 - Query Features 1: Pre-Fetching and Pagination

이해용·2022년 8월 29일
0
post-thumbnail

※ 본 강의에서의 react query는 3버전으로 4버전과는 버전 차이가 있어 코드가 다를 수 있습니다.

Prefetch and Pagination (캐시에 데이터 추가하기)

Options for pre-populating data

where to use?data from?added to cache?
prefetchQuerymethod to queryClientserveryes
setQueryDatamethod to queryClientclientyes
placeholderDataoption to useQueryclientno
initialDataoption to useQueryclientyes

prefetchQuery - 데이터는 서버에서 오기 때문에 데이터를 가져오기 위해 서버로 이동하고 데이터는 캐시에 추가 됩니다.

setQueryData - useQuery를 실행하지 않고 쿼리 데이터를 캐시에 추가하는 방법입니다. 이것 또한 queryClient를 사용하는 메서드입니다.
클라이언트에서 데이터를 가져옵니다. 따라서 서버에서 변이(mutations)에 대한 응답으로 나온 데이터일 수 있죠. queryClient에서 setQueryData 메소드를 사용하여 캐시에 데이터를 추가하면 useQuery가 데이터를 요청할 때 캐시가 해당 데이터를 제공하도록 할 수 있습니다.

placeholderData - useQuery를 실행할 때 데이터를 제공하기 때문에 클라이언트에서 데이터를 가져오고 캐시에는 추가되지 않습니다. placeholderData는 고정 값 또는 함수를 사용할 수 있습니다. 자리 표시자 데이터 값을 동적으로 결정하는 함수를 사용하려는 경우 placeholderData를 사용하는 것이 가장 좋습니다.
빈 배열이었던 treatments나 staff와 같이 고정된 값이 있는 경우 반환 배열을 구조분해 할 때 해당 값을 기본값으로 할당하는 것이 좀 더 수월합니다. 따라서 placeholderData는 자리 표시자가 필요한 경우에만 사용합니다. 달리 표시할 데이터가 없는 경우 사용하는 표시용 데이터이일 뿐이며 다시 사용할 일이 없기 때문에 캐시에 추가하지 않습니다.

initialData - 이것 또한 useQuery에 대한 옵션이며 클라이언트에서 제공됩니다. placeholderData와 달리 캐시에 추가되야하는 데이터입니다. 이것이 이 쿼리에 대한 유효한 데이터임을 공식적인 기록에 선언해 둘 필요가 잆습니다.

Prefetch Treatments (프리페칭 처리)

  • saw prefetch with pagination

    • prefetch next page
  • different trigger: prefetch treatments on home page load

    • user research: 85% of home page loads are followed by treatments tab loads
    • Treatments don’t change often, so cached data isn’t really a problem
  • garbage collected if no useQuery is called after cacheTime (캐시 시간 내에 useQuery로 데이터를 호출하지 않으면 가비지 컬렉션으로 수집됩니다. 캐시 시간이 있는 이유가 바로 이것입니다. 캐시 시간 내에 캐시에 보관할 필요가 없는 유용하지 않은 데이터를 구분하고 이를 가비지 컬렉션에 포함합니다.)

    • if typicall not loaded by default cacheTime (5 minutes), specify longer cacheTime (만약 사용자가 기본 캐시 시간 즉, 5분 이내에 Treatments 탭을 로드하지 않는 다면 캐시 시간을 더 길게 지정할 수도 있습니다. 이 쿼리뿐만 아니라 일반적으로도 지정할 수 있습니다.
  • prefetchQuery is a method on the queryClient

    • adding to the client cache (클라이언트 캐시에 추가 되며 일회성입니다.)
  • useQueryClient returns queryClient (within Provider) (queryClient 메소드이므로 queryClient를 반환해야 하며 이를 위해 useQueryClient hook 을 사용합니다. 쿼리 공급자 내에 있는 한 이 훅을 통해 queryClient를 가져올 수 있습니다.

코드

// useTreatments.ts 

export function useTreatments(): Treatment[] {
  const fallback = [];
  const { data = fallback } = useQuery([queryKeys.treatments], getTreatments);
  return data;
}

export function usePrefetchTreatments(): void {
  const queryClient = useQueryClient();
  queryClient.prefetchQuery(queryKeys.treatments, getTreatments);
}

useTreatments와 quertClient.prefetchQuery는 동일한 key를 사용합니다.
여기에서는 키는 캐시에서 어느 useQuery가 이 데이터를 찾아야 하는지 알려 주기 때문에 매우 중요합니다.
그리고 캐시에 있는 이 데이터가 이 useQuery 호출과 일치한다고 알려 주는 것입니다.

// Home.tsx

...
import { usePrefetchTreatments } from '../treatments/hooks/useTreatments';

export function Home(): ReactElement {
  usePrefetchTreatments(); // usePrefetchTreatments 구문 추가

  ...
}

Home 컴포넌트 최상위에 usePrefetchTreatments(); 를 실행한 이유?

우선 Home 컴포넌트는 그다지 동적이지 않습니다. 리렌더가 많지 않습니다. 따라서 이 것이 여러 번 실행 될 것이라는 걱정은 하지 않아도 됩니다. 만약 걱정된다면 staleTime과 cacheTime을 관리해 줄 몇 가지 옵션을 추가하면 됩니다. 그러면 모든 트리거마다 리페칭하지 않도록 설정할 수 있습니다.

컴포넌트가 마운트될 때 한 번만 실행되도록 useEffect를 사용할 수는 없을까요?

useEffect 콜백 내에서 훅을 실행할 수는 없습니다.

결과

메인 화면에서 프리패치

메인 화면에서 Treatments 페이지가 프리페치 되어있는 것을 확인 할 수 있습니다.

Treatments

새로고침 되면서 last updated 시간이 변경됩니다.

useAppointments를 위한 useQuery

...
  const fallback = {};

  const { data: appointments = fallback } = useQuery(
    queryKeys.appointments,
    () => getAppointments(monthYear.year, monthYear.month),
  );

  return { appointments, monthYear, updateMonthYear, showAll, setShowAll };
...

useQuery를 사용하여 데이터를 가져왔으나 아래 처럼 9월이 되었을 때 8월과 동일한 데이터가 입력되는 문제가 발생했습니다.

August

September

Why doesn’t new data load? (의존성 배열로서의 쿼리 키)

  • using the same key for every query (모든 쿼리에 동일한 키를 사용하기 때문입니다.)
  • after clicking arrow to load new month (이전 달이나 다음 달을 클릭하여 로드한 후에)
    • data is stale (staleTime = 0), but… (쿼리 데이터는 만료(stale)상태이지만 리페치(Refetch)를 트리거할 대상이 없습니다.)
    • nothing to trigger refetch (리페치를 트리거하려면…)
      • component remount
      • window refocus
      • running refetch function manually (리페치 함수를 수동으로 실행시킵니다.)
      • automated refetch
  • Only fetch new data for known key for the above reasons
  • Solution? New key for each month
    • treat keys as dependency arrays!

코드

...
const fallback = {};

  const { data: appointments = fallback } = useQuery(
    [queryKeys.appointments, monthYear.year, monthYear.month],
    () => getAppointments(monthYear.year, monthYear.month),
  );
...

의존성 배열 적용

위의 화면에서는 문제가 없으나 개발자 도구에서 slow 3g로 속도 설정을 하면 데이터를 받아오는 시간이 길어져 prefetch를 적용을 해야합니다.

keepPreviousData

...
const fallback = {};

  const { data: appointments = fallback } = useQuery(
    [queryKeys.appointments, monthYear.year, monthYear.month],
    () => getAppointments(monthYear.year, monthYear.month),
    {
      keepPreviousData: true,
    },
  );
...

keepPreviousData: true

하지만 개발자 도구에서 slow 3g로 선택하고 9월로 이동했을 때 데이터 로드시에 8월과 9월 데이터가 겹쳐졌다가 9월 데이터로 변경되고 10월로 이동했을 때도 동일한 현상이 발생됩니다.

따라서, keepPreviousData는 여기서 맞지 않으며 배경과 데이터가 일치하도록 해야합니다.

데이터를 프리패치해야합니다.

Pre-fetch for appointments pagination

내가 짠 코드

export function usePrefetchAppointments(): void {
    const queryClient = useQueryClient();
    queryClient.prefetchQuery(queryKeys.appointments, getAppointments);
  }

실제 적용 코드

const queryClient = useQueryClient();
  useEffect(() => {
    const nextMonthYear = getNewMonthYear(monthYear, 1);
    queryClient.prefetchQuery(
      [queryKeys.appointments, nextMonthYear.year, nextMonthYear.month],
      () => getAppointments(nextMonthYear.year, nextMonthYear.month),
    );
  }, [queryClient, monthYear]);

useEffect는 프리페치를 트리거하는 방법이고
useQueryClient는 프리페치 메서드를 실행할 QueryClient를 얻는 방법입니다.

useQueryClient를 실행해 queryClient를 만듭니다.

queryClient와 monthYear라는 의존성 배열을 가진 useEffect가 있습니다.

monthYear가 업데이트될 때마다, 예를 들어, 누군가가 증분 버튼을 클릭하여 updateMonthYear가 호출되면 현재 MonthYear 값과 한 달이라는 증분을 기반으로 하는 nextMonthYear를 얻게 되고 쿼리를 프리페칭하게 됩니다. 쿼리 키는 이 의존성 배열이 되며 appointments, year, month로 식별되게 됩니다.

서버 호출, 즉 쿼리 함수는 미래의 월과 연도가 포함된 getAppointments 입니다.

따라서 이 프리페치의 효과는 이 데이터로 캐시를 채워서 사용자가 다음 달을 클릭할 때 표시되게 하는 것입니다.

pre-fetch 적용

8월 예약화면이 나오면 9월 데이터가 프리페칭되고 9월로 이동하면 10월 데이터가 프리페칭되는 것을 볼 수 있습니다.

Summary

  • Pre-populating data options:
    • pre-fetch, setQueryData, placeholderData, initialData
  • Pre-fetch to pre-populate cache
    • on component render
    • on page (month/year) update
    • keepPreviousData only useful if background doesn’t change
  • Treat keys as dependency arrays

reference
https://www.udemy.com/course/learn-react-query
https://tanstack.com/query/v4/docs/reference/QueryClient#queryclientprefetchquery
https://tanstack.com/query/v4/docs/guides/paginated-queries?from=reactQueryV3&original=https://react-query-v3.tanstack.com/guides/paginated-queries#better-paginated-queries-with-keeppreviousdata

profile
프론트엔드 개발자입니다.

0개의 댓글