yehey's 공부 노트 \n ο(=•ω<=)ρ⌒☆

[Next js/react-query] react query 설치 및 적용하기 본문

개발/프로젝트

[Next js/react-query] react query 설치 및 적용하기

yehey 2023. 12. 31. 20:28

Today I Learned

: React query 라이브러리를 Next js 에 적용하고 사용해보았다.

배경

프로젝트 진행 중 서버와 api 통신 과정을 react query + axios를 이용하여 진행하기로 하였다. fetch 가 Next 공식 문서 상에 있지만 기본 url 정보나 인터셉터 등등을 지정해놓고 사용하기에는 axios 가 더 적절하다고 판단했다. 굳이 react query를 사용한 이유는 서버 데이터 관리, 캐싱 등을 통해 성능을 높일 수 있다고 생각하여 한번 적용해보고자 했다. 동일한 데이터 요청을 중복으로 처리하지 않고 기존 응답 값을 캐싱하고 캐싱 값을 사용한다는 점에서 시도해볼 가치가 있다고 생각해서 적용했다. 그리고 무엇보다 hook 과 비슷한 구조를 가지고 있어서 초기 학습은 어렵지 않아서 좋았다. (초기만 안어렵고 잘 쓰기는 결국 어려운 듯...)

무엇보다 devtool 이 잘 되어있어서 좋았다!! 역시 눈으로 보는게 훨씬 이해가 잘 간다.

Contents

react query 설치

npm install @tanstack/react-query
npm install @tanstack/react-query-devtools
// utils/ReactQueryProvider.tsx
'use client';

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental';
import React from 'react';

function ReactQueryProvider({ children }: React.PropsWithChildren) {
  const [queryClient] = React.useState(
    () =>
      new QueryClient({
        defaultOptions: { queries: { staleTime: 5000 } },
      }),
  );

  return (
    <QueryClientProvider client={queryClient}>
      <ReactQueryStreamedHydration>{children}</ReactQueryStreamedHydration>
      <ReactQueryDevtools />
    </QueryClientProvider>
  );
}
export default ReactQueryProvider;

Next js Approuter 를 사용했다.

// app/layout.tsx
export default function RootLayout({ children }: IRootLayoutProps) {
  return (
    <html lang="kr">
      <body>
        <div id="portal" />
            <ReactQueryProvider>{children}</ReactQueryProvider>
      </body>
    </html>
  );
}

이제 react query 사용 준비는 끝났다.

Example / Practice

useQuery

  const { data: selectableTagList,isLoading,isError } = useQuery({
    queryKey: ['rootTag', typeID],
    queryFn: () => getRootTagList(typeID),
  });

react hook을 사용하는 것처럼 사용할 수 있다. queryKey 를 기반으로 값을 캐싱하기 때문에 적절한 queryKey를 입력하는 것이 중요할 것 같다.

useSuspenseQuery

  const { data: statisticList } = useSuspenseQuery({
    queryKey: ['statisticList', filter.archiveTypeID, filter.start, filter.end],
    queryFn: () => getRootTagStatisticListByArchiveType(filter),
  });

이전 버전에서는 useQuery 에 suspense:true 옵션을 주고 Suspense와 함께 사용했는데, v5 부터는 useSuspenseQuery를 통해 suspense와 사용할 수 있다.

function StatisticPage() {
  return (
    <Layout>
      <Suspense fallback={<Loading />}>
        <Statistic />
      </Suspense>
    </Layout>
  );
}

위와 같이 Suspense 로 감싸주면 사용할 수 있다.

useMutation

  const { mutate } = useMutation({
    mutationFn: (data: LedgerCreateParams) => postLedger(data),
    onSuccess: () => {
      alert('내역 저장 완료');
      setForm(defaultForm);
    },
    onError: () => {
      alert('내역 저장 실패');
    },
  });

mutate 동작을 원하는 이벤트 핸들러에 넣어주면 된다.

useQueries

병렬적으로 쿼리를 수행하고 싶을 때 사용할 수 있다.

export const useChildTagList = (tagList: number[]) => {
  const [childTagList, setChildTagList] = useState<TagType[]>([]);

  const queryResult = useQueries({
    queries: tagList.map((tag) => {
      return { queryKey: ['childTag', tag], queryFn: () => getChildTagList(tag) };
    }),
    combine: (results) => {
      return {
        data: results.map((result) => {
          return result.data;
        }),
        pending: results.some((result) => result.isPending),
      };
    },
  });

combine 함수에서 data 를 반환하도록 추가할 수 있다. 데이터를 사용할 때는 queryResult.data 로 사용할 수 있다.
https://github.com/TanStack/query/discussions/2440 안정적인 success 이후 데이터를 가져오도록 하는 방법도 있다.

invalidateQueries 쿼리 무효화

특정 mutation 이후에 다른 query 를 동시에 업데이트 하고 싶을 때 사용했다. 강제로 쿼리를 최신 상태로 만들어준다. 쿼리가 무효화되기 때문에 최신상태 쿼리를 다시 get 하는 방식으로 추정.. key를 기반으로 무효화하기 때문에 관련 쿼리가 모두 무효화된다는 점을 주의..

  const queryClient = useQueryClient();

  const { mutate } = useMutation({
    mutationFn: (data: TagCreateParams) => postTag(data),
    onSuccess: () => {
      setTagForm(defaultForm);
      if (tagForm.parentID) {
        queryClient.invalidateQueries({ queryKey: ['childTag', tagForm.parentID] });
      }
      queryClient.invalidateQueries({ queryKey: ['rootTag', tagForm.archiveTypeID] });
      closeModal();
    },
    onError: () => {
      alert('태그 생성 실패');
    },
  });

비슷하게 setQueryData도 있는데, 이건 아직 사용해보지 않았다. 쿼리를 완전 무효화하는게 아니라 즉시 업데이트 한다는 점이 차이인 듯

Learned

react query를 사용해보며 처음에는 graphql, apollo client 랑 크게 다를 것 없다고 생각했는데 기능적으로 완전히 달랐다. 그치만 graphql 덕에 활용 자체가 어렵진 않았다. devtools 를 통해서 캐시데이터 상태를 확인하니 이해에 더 도움이 됐다. 그치만 아직은 부족함,,,,,


What I Will Study

정리한 부분은 진짜 기본 사용 정도라서 관련 react query 옵션이나 함수들을 찾아서 잘 쓰고싶다.
stale time 이나 cache, refetch 등등 많던데 꼭 잘 써봐야징

참조

https://github.com/ssi02014/react-query-tutorial
https://tanstack.com/query/v5/docs/react/overview

Comments