⚛️React | 리액트 쿼리(React Query) 간단히 사용하기 (useQuery, useMutation)

dayannne·2023년 11월 23일
1

React

목록 보기
6/13
post-thumbnail

React Query는 React Application에서 서버의 상태를 불러오고, 캐싱하며, 지속적으로 동기화하고 업데이트 하는 작업을 도와주는 라이브러리이다.
프로젝트에서 조금 더 규격화된 API 요청 방식 고려와 useEffect 사용을 줄여 복잡한 코드구조를 개선하기 위해 사용하게 되었다.

  • Redux를 사용할 경우 Redux의 기본 원칙 준수를 위한 다양한 Boilerplate 코드들이 필요한데, React Query를 사용하면 Redux와 비교해 코드의 분량을 확연히 줄일 수 있다.
  • 비동기 과정을 선언적으로 관리할 수 있다.
  • get을 한 데이터에 대해 update가 되면 자동으로 get을 다시 수행한다.
  • 데이터가 오래 되었다고 판단되면 다시 get(invalidateQueries) 할 수 있는 기능도 존재한다.
  • React Query는 API 요청을 Query 그리고 Mutation 이라는 두 가지 유형으로 나뉜다.

리액트 설치

npm install react-query


(선택)리액트 쿼리 개발자 도구

리액트 쿼리 개발자 도구를 세팅한다.
개발자 도구를 통해 캐시로 저장하는 useQuery 데이터를 작업하면서 실시간으로 볼 수 있다.

  • import { ReactQueryDevtools } from 'react-query/devtools'

실행시 브라우저에서 위와 같이 하단바에 개발자 도구가 나타나는 것을 확인할 수 있다.


useQuery

React Query 라이브러리에서의 useQuery 훅은 API 요청을 처리하고 상태를 관리하는 기능을 제공한다.
이를 통해 데이터를 가져오고(GET), 캐싱하며, 상태 변화에 따라 자동으로 업데이트할 수 있다.

데이터를 가져오는 것 뿐만 아니라, 데이터 요청 로딩 상태까지 받아올 수 있는 것이 장점이다.

이 점을 활용하여 useQuery로 API GET 요청을 보내 상품 데이터를 받아오는 커스텀 훅을 프로젝트에 적용하였다.

useQuery 훅 만들기

1. (생략 가능)

내 코드의 경우 데이터 요청에 성공했을 때에만 데이터를 변경해 return하고자 useState로 관리하는 productData를 정의해 두었다.

export const useGetProduct = (product_id: number) => {
  
  const [productData, setProductData] = useState<ProductRes>(initialProductState as ProductRes);

  /* ... */
}

2. API 요청 함수

   const getProduct = async (product_id: number): Promise<ProductRes> => {
    const res = await urlInstance.get(`/products/${product_id}/`); // 커스텀한 axios 인스턴스를 통해 API 요청
    return res.data;
  };
}

useGetProduct 훅 내 GET 요청을 보내 상품 데이터를 가져오는 async/await 함수를 생성한다.

3. useQuery 적용

const { isLoading } = useQuery(['product', product_id], () => getProduct(product_id), {
    onSuccess: (data) => {
      setProductData(data);
    },
  });

  return {
    productData,
    isProductLoading: isLoading,
  };

useQuery 훅을 사용하여 API 요청을 처리한다.

  • 첫번째 인자에 ['product', product_id]라는 쿼리 키를 정의해서 캐싱을 위해 사용되도록 한다.

  • 두번째 인자로는 getProduct 함수를 전달하여 실제 API 요청을 수행한다.

  • onSuccess 옵션을 설정하여 요청이 성공할 경우에만 setProductData를 통해 상태를 업데이트한다. (그 외 onError 등 요청 결과에 대한 다양한 옵션 설정 가능)

  • 마지막으로, productDataisLoading가 담긴 객체를 반환한다. productData는 가져온 상품 데이터를 담고 있으며, isLoading은 API 요청이 진행 중인지를 나타내는 상태값이다.

전체 코드

// useGetProduct

import { useQuery} from 'react-query';
import { axiosInstance, imgInstance, urlInstance, userInstance } from 'src/api/axiosInstance';

/* ... */

export const useGetProduct = (product_id: number) => {
  const [productData, setProductData] = useState<ProductRes>(initialProductState as ProductRes);
  const getProduct = async (product_id: number): Promise<ProductRes> => {
    const res = await urlInstance.get(`/products/${product_id}/`); // 커스텀한 axios 인스턴스를 통해 API 요청
    return res.data;
  };

  const { isLoading } = useQuery(['product', product_id], () => getProduct(product_id), {
    onSuccess: (data) => {
      setProductData(data);
    },
  });

  return {
    productData,
    isProductLoading: isLoading,
  };
};

useQuery 훅 적용하기

import { useGetProduct } from 'src/hooks/useProduct';

/* ... */

const ProductDetailCard = () => {

const { productData, isProductLoading } = useGetProduct(productId);

   return (
    productData &&
    !isProductLoading && (
      {/*태그*/}
    )
   )
  
}

useMutation

useMutation 훅은 React Query 라이브러리에서 제공하는 훅으로, 데이터를 생성(POST), 수정(PUT), 삭제(DELETE)하는 API 요청을 처리하고 관리하는 기능을 제공한다.
이를 통해 데이터의 변경 작업을 수행하고, 자동으로 캐싱 및 상태 업데이트를 처리할 수 있다.

다음은 프로젝트에 적용했던 상품 등록을 위한 useMutation 커스텀 훅 코드이다.

useMutation 훅 만들기

1. import

import { useMutation, useQueryClient } from 'react-query';

export const usePostProduct = () => {
  const queryClient = useQueryClient();

  /* ... */
}

queryClient는 useQueryClient 훅을 사용하여 초기화된 React Query의 queryClient 인스턴스를 가져오는 역할을 한다. 이 인스턴스를 사용하여 쿼리를 무효화(invalidate)할 수 있다.

2. API POST 요청 함수 만들기

const addProduct = async (data: ProductReq) => {
    const res = await imgInstance.post<ProductReq>(`/products/`, data);
    return res.data;
  };

async/await를 사용하여 POST 요청을 보내는 addProduct라는 함수를 usePostProduct 커스텀 훅 내에 만들었다. 마찬가지로 axios instance를 활용했으며, POST 요청을 보내 제품 데이터를 생성하여 응답으로 받은 데이터는 res.data로부터 추출되어 반환된다.

3. useMutation 훅 사용

 return useMutation(async (data: ProductReq) => addProduct(data), {
    onSuccess: () => {
      queryClient.invalidateQueries(['product']);
    },
    onError: (error: any) => {
      throw error;
    },
  });
  • 첫 번째 인자로는 비동기 함수인 addProduct를 전달하여 실제 API 요청을 수행한다.
  • 두 번째 인자로는 옵션 객체를 전달하여 성공 또는 실패 시의 동작을 정의한다.
  • API 요청에 성공한 경우 onSuccess 옵션의 콜백함수가 호출된다. useQuery와 달리 queryClient.invalidateQueries(['product'])를 호출하여 'product' 쿼리를 무효화한다. (이를 통해 캐시된 'product' 쿼리 데이터가 업데이트되고, 다시 필요한 경우 새로운 데이터를 가져올 수 있다.)
  • API 요청에 실패한 경우 onError 옵션의 콜백함수가 호출된다. 여기에서는 에러를 던져 예외 처리를 위임한다.

전체 코드

import { useMutation, useQueryClient } from 'react-query';

export const usePostProduct = () => {
  const queryClient = useQueryClient();

  const addProduct = async (data: ProductReq) => {
    const res = await imgInstance.post<ProductReq>(`/products/`, data);
    return res.data;
  };

  return useMutation(async (data: ProductReq) => addProduct(data), {
    onSuccess: () => {
      queryClient.invalidateQueries(['product']);
    },
    onError: (error: any) => {
      throw error;
    },
  });
};

useMutation 훅 적용하기


import { usePostProduct, ProductReq } from 'src/hooks/useProduct';

/* ... */ 

const ProductAddForm = () => {
const usePostProductMutate = usePostProduct();
const [inputValues, setInputValues] = useState<ProductReq>({
    product_name: '',
    image: null,
    price: 0,
    shipping_method: '',
    shipping_fee: 0,
    stock: 0,
    product_info: '',
  });
  
    const handleInputChange = (
    e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
  ) => {
    const { name, value } = e.target;
    setInputValues((prevInputValues) => ({
      ...prevInputValues,
      [name]: value.trim(),
    }));
  };
  
    // 저장하기 버튼 클릭시 handleSaveBtnClick 함수 실행
    const handleSaveBtnClick = async () => {
    try {
      const response = await usePostProductMutate.mutateAsync(inputValues); // mutate 설정
      if (response) {
        alert('상품이 등록되었습니다.');
        navigate(`/seller/dashboard`);
      } // Post 요청 보내기
    } catch (error: any) {
      // 예외 메시지를 이용해 모달 타입 설정
      console.error(error);
    }
  };
  
  return(
    {/* ... */}
     <Button onClick={handleSaveBtnClick} width='200px'>
         저장하기
     </Button>
     {/* ... */}
  )

참고

카카오페이 프론트엔드 개발자들이 React Query를 선택한 이유

profile
☁️

0개의 댓글