본문 바로가기
카테캠/2단계

[7/22] useMutation으로 바꾸기

by 쪼꼬에몽 2025. 7. 23.
import { useNavigate } from 'react-router-dom';
import { toast } from 'react-toastify';
import { getUserInfo } from '@/storage/userInfo';
import { PATH } from '@/constants/path';
import order from '@/api/order';
import { renderOrderSuccessToast } from '@/utils/toastContents';
import type { OrderRequest } from '@/api/order';

interface UseOrderSubmitParams {
  product: { id: number; name: string; price: number };
  count: number;
  receiverRef: React.MutableRefObject<{ name: string; phone: string; count: number }[] | null>;
}

export default function useOrderSubmit({ product, count, receiverRef }: UseOrderSubmitParams) {
  const navigate = useNavigate();

  return async (data: any) => {
    const userInfo = getUserInfo();
    const token = userInfo?.authToken;

    if (!token) {
      toast.error('로그인이 필요합니다.');
      navigate(PATH.LOGIN);
      return;
    }

    if (!receiverRef.current || receiverRef.current.length === 0) {
      toast.error('받는 사람 정보를 입력해주세요.');
      return;
    }

    const { textMessage, senderName, messageCardId } = data;

    const orderData: OrderRequest = {
      productId: product.id,
      message: textMessage,
      messageCardId: String(messageCardId),
      ordererName: senderName,
      receivers: receiverRef.current.map(({ name, phone, count }) => ({
        name,
        phoneNumber: phone,
        quantity: Number(count),
      })),
    };

    try {
      const result = await order(orderData);
      if (result.data?.success) {
        toast(renderOrderSuccessToast(product.name, count, senderName, textMessage), {
          type: 'success',
          autoClose: 3000,
          style: { width: '400px' },
        });
        navigate(PATH.HOME);
      }
    } catch (error) {
      toast.error(error instanceof Error ? error.message : '주문 중 오류가 발생했습니다.');
    }
  };
}

위 코드는 react query 없이 작성한 코드이다.

가장 아래 부분의 try catch를 useMutation을 사용해서 바꿀 것이다.

import { useNavigate } from 'react-router-dom';
import { toast } from 'react-toastify';
import { getUserInfo } from '@/storage/userInfo';
import { PATH } from '@/constants/path';
import order from '@/api/order';
import { renderOrderSuccessToast } from '@/utils/toastContents';
import type { OrderRequest } from '@/api/order';
import { useMutation } from '@tanstack/react-query';

interface UseOrderSubmitParams {
  product: { id: number; name: string; price: number };
  count: number;
  receiverRef: React.MutableRefObject<{ name: string; phone: string; count: number }[] | null>;
}

export default function useOrderSubmit({ product, count, receiverRef }: UseOrderSubmitParams) {
  const navigate = useNavigate();

  const mutation = useMutation({
    mutationFn: (orderData: OrderRequest) => order(orderData),
    onSuccess: (_, variables) => {
      toast(
        renderOrderSuccessToast(
          product.name,
          count,
          variables.ordererName,
          variables.message
        ),
        {
          type: 'success',
          autoClose: 3000,
          style: { width: '400px' },
        },
      );
      navigate(PATH.HOME);
    },
    onError: (error) => {
      toast.error(error instanceof Error ? error.message : '주문 중 오류가 발생했습니다.');
    },
  });

  return async (data: {
    textMessage: string;
    senderName: string;
    messageCardId: string | number;
  }) => {
    const userInfo = getUserInfo();
    const token = userInfo?.authToken;

    if (!token) {
      toast.error('로그인이 필요합니다.');
      navigate(PATH.LOGIN);
      return;
    }

    if (!receiverRef.current || receiverRef.current.length === 0) {
      toast.error('받는 사람 정보를 입력해주세요.');
      return;
    }

    const { textMessage, senderName, messageCardId } = data;

    const orderData: OrderRequest = {
      productId: product.id,
      message: textMessage,
      messageCardId: String(messageCardId),
      ordererName: senderName,
      receivers: receiverRef.current.map(({ name, phone, count }) => ({
        name,
        phoneNumber: phone,
        quantity: Number(count),
      })),
    };

    await mutation.mutateAsync(orderData);
  };
}
mutationFn: (orderData: OrderRequest) => order(orderData),

 

- 함수형으로 만든 이유는 순차적으로 나와야 하기 때문이다. orderData가 생성되어야 하기 때문에 함수형으로 만들었다.

onSuccess: (_, variables) => {

- variables는 성공한 것이 아닌 만든 것들을 모두 참조한다. 앞의 _는 성공한 것을 주는데 여기서는 필요 없으므로 _로 둔다.

await mutation.mutateAsync(orderData);

- return문이 async이기 때문에 await와 mutateAsync로 둔다.

 

  • 이 함수가 async 함수이므로
  • 주문 요청이 완료될 때까지 기다렸다가, 에러가 발생하면 try/catch 블록에서 잡아내거나 다음 작업을 진행할 수 있게 하기 위해서
  • 예를 들어, 주문 성공 후 다음 작업이 있을 때 (여기선 onSuccess 콜백도 있지만) 좀 더 명확하게 비동기 흐름을 제어하려고

 

 

 

  • mutate
    • 콜백 함수 방식
    • 성공/실패 콜백을 onSuccess, onError 옵션에서 처리
    • 호출 직후 바로 리턴 (비동기 처리 완료 여부를 알 수 없음)
  • mutateAsync
    • Promise 반환
    • await로 호출 가능해서 try/catch 구문으로 에러 처리가 가능
    • 함수가 끝날 때까지 기다릴 수 있어서, 그 이후 로직을 쉽게 순차 처리 가능

 

 

'카테캠 > 2단계' 카테고리의 다른 글

[7/22] useMutation으로 바꾸기  (3) 2025.08.06
[7/23] Suspense & ErrorBoundary  (3) 2025.07.23
[7/22] React Query - queryClient  (0) 2025.07.22
[7/22] React Query - useMutation  (1) 2025.07.22
[7/22] React Query - useQuery  (0) 2025.07.22