✏️기록하는 즐거움
article thumbnail
반응형

개발 환경
next: v14.2.15
@tanstack/react-query: v5.60.6
@tanstack/react-query-devtools: v5.60.6

 

오늘은 개인 프로젝트에 Tanstack Query(React Query)를 적용하는 과정 중에 여러 코드에서 볼 수 있던 QueryClient에 useState를 적용하는 이유에 대해서 작성해보고자 한다.

 

📙 설치

npm i @tanstack/react-query
yarn add @tanstack/react-query

 

📙 적용

🟡 Provider 설정

NextJS에서는 use client 로 Provider를 클라이언트 컴포넌트로 변경해주어야 에러가 발생하지 않는다.
// RQProvider.tsx
"use client";

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

type Props = {
  children: React.ReactNode;
};

export default function RQProvider({ children }: Props) {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        refetchOnWindowFocus: false,
        refetchOnReconnect: false,
        retry: false,
      },
    },
  });

  return (
    <QueryClientProvider client={queryClient}>
      {children}
      <ReactQueryDevtools />
    </QueryClientProvider>
  );
}

첫 단계로는, 애플리케이션에 QueryClient을 연결하고 제공하기 위해 Provider를 설정해주어야 한다.

 

Query Client

Query Client는 캐시와 상호 작용하는 데 사용한다.

defaultOptions 를 사용하여 모든 쿼리와 뮤테이션에 대한 기본 값을 정의할 수 있다.

  • refetchOnWindowFocus
    • default: true
    • 화면에 초점을 다시 맞출 때 쿼리가 데이터 패치를 재시도할 것인지에 대한 여부
  • refetchOnReconnect
    • default: true
    • 네트워크 연결이 다시 설정될 때 쿼리가 데이터 패치를 재시도할 것인지에 대한 여부
  • retry
    • 요청 실패 시 쿼리나 뮤테이션을 몇 번 재시도해야 하는지 지정
    • 쿼리는 기본적으로 3번, 뮤테이션은 자동으로 재시도되지 않는다.
    • 재시도를 비활성하려면 false 를, 무한 재시도는 true 로 설정한다.

 

🟡 QueryClient 생성 시 useState 는 왜 사용할까?

function App() {
  const [queryClient] = useState(() => new QueryClient())
  return (
    <QueryClientProvider client={queryClient}>
      <Home />
    </QueryClientProvider>
  )
}

여러 프로젝트 코드를 보면 useState를 사용하여 Query Client를 생성한 코드를 볼 수 있었다.

기존 코드와 어떤 차이점이 있을까?

 

기존 코드는 Provider 컴포넌트 안에서 Query Client를 생성하고 있었다.

// 기존 코드
export default function RQProvider({ children }: Props) {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: {
        refetchOnWindowFocus: false,
        refetchOnReconnect: false,
        retry: false,
      },
    },
  });

이는 Quey Client가 일반 변수로 선언되어 렌더링이 발생할 때마다 queryClient가 새로 생성된다.

따라서 공식 문서에서도 참고할 수 있듯이 위와 같은 패턴은 안티 패턴으로 정의하고 있다.

 

이를 해결하기 위해서는 3가지 방법이 존재한다.

 

1. useState 사용

function App() {
  const [queryClient] = useState(() => new QueryClient())
  return (
    <QueryClientProvider client={queryClient}>
      <Home />
    </QueryClientProvider>
  )
}

 

2. 컴포넌트 외부에 정의

const queryClient = new QueryClient()
function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Home />
    </QueryClientProvider>
  )
}

 

3. prefetchQuery() 사용

async function App() {
  const queryClient = new QueryClient()
  await queryClient.prefetchQuery(options)
}

 

3가지 모두 Query Client 인스턴스가 한번만 생성된다.

prefetchQuery의 경우 캐시에 해당 데이터가 있다면, 캐시에 있는 데이터를 반환하여 추가로 생성하지 않게 된다.

 

나는 이 중 useState 를 사용하는 방식을 채택했다.

export default function RQProvider({ children }: Props) {
  const [queryClient] = useState(
    new QueryClient({
      defaultOptions: {
        queries: {
          refetchOnWindowFocus: false,
          refetchOnReconnect: false,
          retry: false,
        },
      },
    }),
  );

  return (
    <QueryClientProvider client={queryClient}>
      {children}
      <ReactQueryDevtools />
    </QueryClientProvider>
  );
}

useState 를 선택한 이유는 제일 리액트스럽다 생각했기 때문이다.

useState 를 통해 Query Client 인스턴스를 생성하면 React 컴포넌트의 상태로 관리하게 된다.

React의 상태 초기화는 첫 렌더링 시 한 번만 수행되어 컴포넌트의 렌더링 간에 같은 인스턴스를 유지하도록 보장하기 때문에 인스턴스가 한번만 생성되는 것이다.

 

🟡 적당한 위치에 Provider 적용시키기

import { ReactNode } from "react";

import RQProvider from "./_component/RQProvider";
import Header from "@/_component/common/Header";

export default function HeaderLayout({ children }: { children: ReactNode }) {
  return (
    <>
      <Header />
      <RQProvider>{children}</RQProvider>
    </>
  );
}

 

쿼리 패칭이 필요한 부분에 Provider를 적용시킨다.

 

📙 Devtools

npm i @tanstack/react-query-devtools
yarn add @tanstack/react-query-devtools
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'

 

Devtools는 기본적으로 process.env.NODE_ENV === 'development' 인 경우에만 번들에 포함되어 개발 환경에서만 동작한다.

 

import { ReactQueryDevtools } from '@tanstack/react-query-devtools'

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      {/* The rest of your application */}
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  )
}
  • initialIsOpen
    • default: false
    • Devtools가 기본적으로 열려 있게 하려면 true로 설정한다.

 

그 외에도 Devtools를 열고 닫을 수 있는 로고 버튼의 위치, 패널의 위치도 변경할 수 있고, QueryClient를 여러개 사용할 경우 Devtools를 사용할 QueryClient를 설정하는 등 여러가지 옵션을 지정할 수 있다.

 

Devtools button

 

Devtools Pannel

Devtools를 적용하면 기본적으로는 화면 우측 하단에 Devtools Pannel을 열고 닫을 수 있는 버튼이 생성되며, 버튼을 누르면 패널이 열려서 쿼리 패칭 동작을 시각적으로 볼 수 있다.

 

그동안 프로젝트에서 Tanstack Query를 여러번 적용해봤었지만 useState를 통해 적용해본 것은 처음이라 왜 많은 코더들이 이렇게 적용하는 것인지 궁금증이 생겨 한번 알아보게 되었다.

코딩을 할 때 여러 방면에서 최적화된 코드에 대해 고민하면서 코드를 작성해야 함을 깨닫게 되는 과정이었다 ! :)

반응형
profile

✏️기록하는 즐거움

@nor_coding

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!