느려도 한걸음씩

TanStack Query - (4) useMutation으로 데이터 변형하기 본문

FE develop/TanStack Query

TanStack Query - (4) useMutation으로 데이터 변형하기

hoj0806 2025. 4. 10. 03:28

 

 

이번 포스트에서는 useMutation 훅을 사용해 서버에서 데이터를 삭제(변형) 하는 방법을 소개합니다. 앞에서 배운 useQuery가 서버에서 데이터를 가져오는(fetch) 역할이었다면, useMutation은 데이터를 수정, 생성, 삭제(mutate) 할 때 사용됩니다.

 

 

🔄 Supabase에서 row 삭제하기

먼저 Supabase의 API 문서에서 Deleting matching rows 예제를 참고해, 특정 캐빈 데이터를 삭제하는 코드를 작성해볼게요.

 

위의 문서에 있는 코드를 토대로 함수를 만들어 줍니다.

export async function deleteCabin(id) {
  const { data, error } = await supabase.from("cabins").delete().eq("id", id);

  if (error) {
    console.error(error);
    throw new Error("Cabins could not be deleted");
  }

  return data;
}

 

  • supabase.from("cabins").delete().eq("id", id)를 통해, 지정된 id 값을 가진 캐빈 데이터를 삭제합니다.
  • 오류가 발생하면 예외를 던지고, 성공하면 삭제된 데이터를 반환합니다.

 

💡 CabinRow 컴포넌트에서 삭제 구현

이제 이 deleteCabin 함수를 실제 컴포넌트에서 useMutation으로 사용해볼게요.

import styled from "styled-components";
import { formatCurrency } from "../../utils/helpers";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { deleteCabin } from "../../services/apiCabins";

const TableRow = styled.div`
  display: grid;
  grid-template-columns: 0.6fr 1.8fr 2.2fr 1fr 1fr 1fr;
  column-gap: 2.4rem;
  align-items: center;
  padding: 1.4rem 2.4rem;

  &:not(:last-child) {
    border-bottom: 1px solid var(--color-grey-100);
  }
`;

const Img = styled.img`
  display: block;
  width: 6.4rem;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  object-position: center;
  transform: scale(1.5) translateX(-7px);
`;

const Cabin = styled.div`
  font-size: 1.6rem;
  font-weight: 600;
  color: var(--color-grey-600);
  font-family: "Sono";
`;

const Price = styled.div`
  font-family: "Sono";
  font-weight: 600;
`;

const Discount = styled.div`
  font-family: "Sono";
  font-weight: 500;
  color: var(--color-green-700);
`;

function CabinRow({ cabin }) {
  const {
    id: cabinId,
    name,
    maxCapicity,
    regularPrice,
    discount,
    image,
  } = cabin;

  const queryClient = useQueryClient();

  const { isLoading: isDeleting, mutate } = useMutation({
    mutationFn: deleteCabin, // ✅ 데이터를 실제로 삭제하는 함수
    onSuccess: () => {
      alert("Cabin successfully deleted");
      queryClient.invalidateQueries({ queryKey: ["cabins"] }); // ✅ 캐시 무효화
    },
    onError: (err) => alert(err.message), // ✅ 에러 처리
  });

  return (
    <TableRow role='row'>
      <Img src={image} />
      <Cabin>{name}</Cabin>
      <div>Fits up to {maxCapicity} guests</div>
      <Price>{formatCurrency(regularPrice)}</Price>
      <Discount>{formatCurrency(discount)}</Discount>
      <button onClick={() => mutate(cabinId)} disabled={isDeleting}>
        Delete
      </button>
    </TableRow>
  );
}

export default CabinRow;

 

 

✅ useMutation이란?

  • 서버에 데이터를 변경하는 요청을 보낼 때 사용하는 TanStack Query의 훅입니다.
  • POST, PUT, DELETE 요청에 적합합니다.
  const queryClient = useQueryClient();

  const { isLoading: isDeleting, mutate } = useMutation({
    mutationFn: deleteCabin, // ✅ 데이터를 실제로 삭제하는 함수
    onSuccess: () => {
      alert("Cabin successfully deleted");
      queryClient.invalidateQueries({ queryKey: ["cabins"] }); // ✅ 캐시 무효화
    },
    onError: (err) => alert(err.message), // ✅ 에러 처리
  });

✅ mutationFn

  • 실제로 데이터를 변경하는 함수입니다.
  • 이 예제에서는 Supabase에서 캐빈을 삭제하는 deleteCabin 함수가 들어갑니다.

✅ onSuccess

  • 요청이 성공했을 때 실행되는 콜백 함수입니다.
  • 여기서는 캐빈 삭제가 성공하면 사용자에게 알림을 띄우고, invalidateQueries를 호출해 캐시된 데이터를 무효화합니다.
queryClient.invalidateQueries({ queryKey: ["cabins"] });
  • 이 코드를 통해 "cabins" 데이터를 다시 패칭하게 되고, UI도 최신 상태로 자동 갱신됩니다.(무효화할 데이터의 queryKey와 똑같은 key를 지정해줘야 합니다
  • invalidateQueries 호출을 위해 queryClient를 불러와야 하기 때문에 useQueryClient 메서드를 이용합니다
  • 삭제 후에도 목록이 즉시 업데이트되는 이유가 바로 이 부분입니다.

✅ onError

  • 에러가 발생했을 때 실행되는 콜백 함수입니다.
  • 사용자에게 알림을 띄우거나, 오류 로그를 남길 수 있습니다.

 

삭제 버튼을 통해 데이터가 삭제되고 UI가 변경되는 모습

 

🧠 마무리

useMutation을 사용하면 서버의 데이터를 안전하게 수정하고, React Query의 캐싱 기능과 함께 UI까지 깔끔하게 업데이트할 수 있습니다. 특히 useQueryClient와 invalidateQueries를 조합하면 사용자 경험을 해치지 않고 실시간 반영되는 UI를 만들 수 있다는 점이 큰 장점입니다.