React query 완전 정복, Custom hook으로 관심사 분리하여 사용하기

boyeonJ·2023년 12월 15일
2

React

목록 보기
29/30

React query 완전 정복

본문

1. React query key Enum으로 관리하기

// hooks/items.hooks.ts
import { useQuery } from "react-query";

export enum ServerStateKeysEnum {
  Items = 'items'
} 

2. React query cutom hook으로 감싸기

데이터 계층과 프레젠테이션 계층을 분리하여 관심사를 명확히 구분할 수 있습니다.
이 말은 즉, API fetch function을 React 컴포넌트에서 가져올 필요가 없다는것을 의미합니다.

// hooks/items.hooks.ts
import { useQuery } from "react-query";

export enum ServerStateKeysEnum {
  Items = 'items'
} 

export const useGetItems = () =>
  useQuery(
    ServerStateKeysEnum.Items,
    () => fetch('https://example.com/feedbacks'),  //Simple fetch function
  );
  
// components/SomeComponent.tsx

import React from "react";
import { useGetItems } from "hooks/items.hooks.ts";

export const SomeComponent: React.FC = () => {
  const { data: items } = useGetItems();
  return <>{ items }</>
}

그리고 이렇게 custom hook으로 만들면 해당 데이터를 props drilling 할 필요가 없어집니다. 자식 컴포넌트에 props로 데이터를 넘길 필요 없이 각 컴포넌트에서 custom hook을 불러오면 됩니다.(stale time을 잘 활용하면 불필요한 api 호출 막을 수 있음)

// components/SomeComponent.tsx

import React from "react";
import { useGetItems } from "hooks/items.hooks.ts";

export const SomeComponent: React.FC = () => {
  const { data: items } = useGetItems();
  return (<>
    <h1>You have {items.length} items</h1>
    {/* 이것은 item에 관심이 없는 일부 중간 구성 요소이며
    	item에 관심이 있는 손자 구성 요소에 도달하기 위해 데이터를 전달할 필요가 없습니다.
    */}
    <SomeRandomChildComponent /> 
  </>);
}

// components/SomeGreatGrandChildrenComponen.tsx

import React from "react";
import { useGetItems } from "hooks/items.hooks.ts";

export const SomeGreatGrandChildrenComponen: React.FC = () => {
  const { data: items } = useGetItems();
  return (<>{items}</>);
}

3. stale time option을 통해 네트워크 비용 감소

위에서 잠깐 언급한 stale time을 잘 활용하면 불필요한 api 호출 막을 수 있음은 아래의 config로 지정할 수 있습니다. 해당 옵션은 프로젝트 전체의 option, 각 useQuery의 option으로 모두 지정할 수 있습니다.

// app.tsx
import { QueryClient, QueryClientProvider } from 'react-query';

const queryCache = new QueryClient({
  defaultOptions: {
    queries: {
      refetchOnWindowFocus: false,
      retry: false,
      staleTime: 30000,
    },
  },
});

const App = () => (
    <QueryClientProvider client={queryCache}>
      <FirstSiblingComponent />
      <SecondSiblingComponent />
    </QueryClientProvider>
);

4. 수동으로 캐시 무효화

만약 products list를 수정할 수 있는 어드민 페이지가 있다고 가정해봅시다.

처음 api에서 products를 불러온 후 staleTime을 infinity로 지정한 후 수정 및 추가 작업을 한 후에만 다시 가져오도록 하고 싶을땐 어떻게 해야 할까요? 바로 react query의 invalidateQueries 매서드를 활용하면 됩니다.

// hooks/items.hooks.ts
import { useMutation, useQueryClient } from "react-query";
import {postCreateNewItem} from './items.api';
import { useQuery } from "react-query";

export enum ServerStateKeysEnum {
  Items = 'items'
} 

export const useGetItems = () =>
  useQuery(
    ServerStateKeysEnum.Items,
    () => fetch('https://example.com/feedbacks'),  //Simple fetch function
  );

export const useCreateItem = () => {
  const cache = useQueryClient();
  return useMutation(postCreateNewItem, {
    onSuccess: () => {
      cache.invalidateQueries(ServerStateKeysEnum.Items);
    }
  });
};

5. custom hook에서 option custom 하기

custom hook으로 useQury, useMutation을 작성하여 활용하려고 하는데 각 컴포넌트마다 옵션이 다른경우가 존재합니다.

5-1. useQuery의 enabled 옵션

만약 useQuery의 경우, 특정 상황에서는 해당 api를 요청하지 않아도 되는 경우 enabled 옵션을 컴포넌트에서 정해야 할 수 있습니다.

// hooks/items.hooks.ts
import { useQuery, UseQueryOptions } from "react-query";

export enum ServerStateKeysEnum {
  Items = 'items'
} 

export const useGetItems = (options?: UseQueryOptions) =>
  useQuery(
    ServerStateKeysEnum.Items,
    () => fetch('https://example.com/feedbacks'),  //Simple fetch function
    {
      ...options
    }
  );

// components/SomeComponent.tsx
export const SomeComponent: React.FC<{ hasValidSubscription: boolean }> = ({
  hasValidSubscription
}) => {
  const { data: items } = useGetItems({
    enabled: hasValidSubscription //If hasValidSubscription === false, query won't be executed
  });
  return (<>{items}</>);
}

5-2. useMutation onError, onSuccess

useMutation의 경우 만약 에러처리, 성공처리가 모든 컴포넌트에서 같다면 custom option이 불 필요하지만 만약 컴포넌트마다 에러처리, 성공처리를 다르게 해주어야 한다면 custom option으로 지정해줄 수 있습니다.

// hooks/items.hooks.ts
import { useMutation } from "react-query";
import {patchItem} from './items.api';
import { useRenderToastMessage } from '../some-helper-hooks/useRenderToastMessage';

export const useMutateItem = (options?: UseMutationOptions) => {
  const toast = useRenderToastMessage();
  return useMutation(patchItem, {
    onSuccess: () => {
      toast.render({
        theme: 'success',
        message: 'Item successfully modified'
      });
    },
    onError: () => {
      toast.notify({
        theme: 'error',
        children: 'Could not modify item'
      });
    },
  });
};


// components/SomeComponent.tsx
export const SomeComponent: React.FC = () => {
  const { mutate } = useMutateItem({
   onSuccess: () => {
      toast.render({
        theme: 'success',
        message: 'Item successfully modified'
      });
    },
    onError: () => {
      toast.notify({
        theme: 'error',
        children: 'Could not modify item'
      });
    },
  });
  
  const { data } = mutate();
  return (<>{data}</>);
}

(+) 실시간..

실시간 업데이트

staleTime: 30000, // 30초 동안 캐시된 데이터를 사용
queryInvalidationInterval: 60000, // 1분마다 서버에 재요청하여 캐시 갱신

vs 소켓

0개의 댓글