Tanstack Query(React Query) useMutation

Hyeon·2025년 2월 11일
1
post-thumbnail

React Query는 서버 상태를 관리하는 라이브러리로, 비동기 작업을 쉽게 다룰 수 있도록 도와줍니다. 그중에서 useMutation 훅은 데이터를 생성, 수정, 삭제하는데 사용됩니다.

useMutaion 기본 개념

useMutation은 데이터를 변경하는 요청(POST, PUT, DELETE)을 보내는데 사용됩니다. React Query에서는 이 작업을 mutation이라고 부릅니다. 서버에 새로운 데이터를 추가하거나 기존 데이터를 업데이트 할 때 사용합니다.

import { useQueryClient, useMutation } from "@tanstack/react-query";

const postData = async (data: { name: string }) => {
  const response = await fetch("/api/data", {
    method: "POST",
    body: JSON.stringify(data),
  });
  return response.json();
};

const MyComponent = () => {
  const queryClient = useQueryClient();

  const mutation = useMutation({
    mutationFn: postData,
    onMutate: async (variables) => {
      console.log("mutation start: ", variables);
    },
    onSuccess: (data) => {
      console.log("success: ", data);
    },
    onError: (error) => {
      console.error("error occured", error);
    },
    onSettled: () => {
      console.log("mutation completed");
      queryClient.invalidateQueries({ queryKey: ["data"] });
    },
  });

  const handleSubmit = async () => {
    const data = { name: "hwang" };
    await mutation.mutateAsync(data);
  };

  return (
    <div>
      <button onClick={handleSubmit}></button>
      {mutation.isIdle && <p>초기 상태</p>}
      {mutation.isPending && <p>요청 진행 중</p>}
      {mutation.isSuccess && <p>성공적으로 요청 완료</p>}
      {mutation.isError && <p>요청 중 에러 발생</p>}
    </div>
  );
};

export default MyComponent;

useMutation의 주요 옵션들

1.mutationFn

서버에 요청을 보내는 함수입니다. 비동기 함수로 데이터를 처리합니다.

const postData = async (data: { name: string }) => {
  const response = await fetch("/api/data", {
    method: "POST",
    body: JSON.stringify(data),
  });
  return response.json();
};

2.onMutate

mutationFn이 실행되기 전에 호출되는 콜백 함수입니다. 변수가 전달되며, 여기에서 작업을 미리 처리할 수 있습니다.

onMutate: async (variables) => {
  console.log("mutation start: ", variables);
},

3.onSuccess

mutationFn이 성공적으로 실행된 후 호출되는 콜백 함수입니다. 서버에서 데이터를 정상적으로 받아왔을 때 처리할 작업을 여기에 작성할 수 있습니다.

onSuccess: (data) => {
  console.log("success: ", data);
},

4.onError

mutationFn이 에러를 반환했을 때 호출되는 콜백 함수입니다. 에러 처리를 여기서 할 수 있습니다.

onError: (error) => {
  console.error("error occured", error);
},

5.onSettled

mutationFn이 완료된 후, 성공과 실패에 관계없이 항상 호출되는 콜백 함수입니다. 주로 캐시 무효화나 후처리를 할 때 유용합니다.

onSettled: () => {
  console.log("mutation completed");
  queryClient.invalidateQueries({ queryKey: ["data"] });
},

useMutation의 상태들

4가지 상태를 가지고 있습니다.

mutation.status: "error" | "idle" | =pending" | "success"

1. isIdle

초기상태로, Mutation이 아직 실행되지 않은 상태입니다.

{mutation.isIdle && <p>초기 상태</p>}

1. isPending

Mutation이 진행 중인 상태입니다. 비동기 함수가 호출되고, 결과를 기다리는 동안 표시됩니다.

{mutation.isPending && <p>요청 진행 중</p>}

1. isSuccess

mutationFn이 성공적으로 실행된 상태입니다.

{mutation.isSuccess && <p>성공적으로 요청 완료</p>}

1. isError

mutationFn이 실행 중 에러가 발생한 상태입니다.

{mutation.isError && <p>요청 중 에러 발생</p>}

낙관적 업데이트(Optimistic Update)

  • 사용자가 요청을 보낸 후, 서버의 응답을 기다리지 않고 미리 상태를 업데이트하는 방법입니다.
  • 낙관적 업데이트를 사용하면, 서버 요청을 기다리는 동안에도 사용자에게 즉각적인 피드백을 제공할 수 있습니다. 예를 들어 사용자가 버튼을 클릭하면 즉시 UI에서 변화가 일어나 서버 응답을 기다리는 동안 사용자 경험이 지연되지 않습니다.
  • onMutate에서 미리 데이터를 업데이트하고, 요청에 성공하면 onSuccess에서 캐시를 업데이트 합니다. 실패한다면 onError에서 업데이트를 되돌립니다.
import { useMutation, useQueryClient } from "@tanstack/react-query";

const postData = async (data: { name: string }) => {
  const response = await fetch("/api/data", {
    method: "POST",
    body: JSON.stringify(data),
  });
  if (!response.ok) {
    throw new Error("Network response was not ok");
  }
  return response.json();
};

const MyComponent = () => {
  const queryClient = useQueryClient();
  const mutation = useMutation({
    mutationFn: postData,
    onMutate: async (variables) => {
      // 낙관적 업데이트: 캐시에서 변경 예정인 데이터를 미리 업데이트
      const previousData = queryClient.getQueryData(["data"]);
      queryClient.setQueryData(["data"], variables);

      // 실패 시 캐시 복구를 위해 이전 데이터를 반환
      return { previousData };
    },
    onError: (error, variables, context) => {
      // 실패 시: 이전 데이터를 되돌리기
      console.error("Error occurred:", error);
      if (context?.previousData) {
        queryClient.setQueryData(["data"], context.previousData);
      }
    },
    onSuccess: () => {
      // 서버에서 성공적으로 처리된 후 캐시 무효화
      queryClient.invalidateQueries({ queryKey: "data" });
    },
    onSettled: () => {
      console.log("mutation settled (success or error)");
    },
  });

  const handleSubmit = async () => {
    const data = { name: "hwang" };
    await mutation.mutateAsync(data);
  };

  return (
    <div>
      <button onClick={handleSubmit}>Submit</button>
      {mutation.isIdle && <p>Idle</p>}
      {mutation.isPending && <p>Pending...</p>}
      {mutation.isSuccess && <p>Success!</p>}
      {mutation.isError && <p>Error occurred!</p>}
    </div>
  );
};
export default MyComponent;

1.onMutate

onMutate에서 캐시 업데이트를 미리 진행합니다. 이때 previousData를 return 해서, 실패했을 때 원래 상태로 되돌릴 수 있도록 합니다. context는 onMutate에서 반환된 데이터를 onError와 onSuccess에서 사용할 수 있도록 전달하는 역할을 합니다.

2.onError

onError에서는 onMutate에서 반환된 previous를 사용하여 캐시를 원래 상태로 복구합니다. 이 과정에서 캐시가 업데이트되기 전에 사용했던 데이터로 되돌립니다.

3.onSuccess

성공적인 응답 후에는 서버에서의 변경을 반영하기 위해 캐시를 무효화합니다.

4.낙관적 업데이트를 항상 해야할까?

케바케로 생각됩니다. 상황에 맞게 사용합시다.

사용해야하는 경우
  • 사용자가 데이터를 추가하거나 수정했을 때, 서버와의 통신 지연없이 UI에서 바로 변화를 확인해야 하는 경우(채팅, 게시판 등)
  • 네트워크가 느리거나 응답 시간이 긴 API를 호출하는 경우
고민해봐야 하는 경우
  • 데이터가 즉시 반영될 필요가 없는 경우
  • 네트워크가 매우 빠르거나 지연이 없는 경우, 응답을 기다려도 UI상 큰 차이가 없는 경우
  • 상태를 미리 업데이트하고, 되돌리는 로직을 처리해야 해서 유지보수 비용이 높아질 수 있습니다.

낙관적 업데이트 후 캐시 무효화가 꼭 필요할까?

onMutate에서 캐시를 미리 업데이트 했다면, 서버 응답을 기다리지 않고 UI에서 바로 변경된 데이터를 볼 수 있게 되므로, 무효화를 하고 다시 가져오는 것이 불필요할 수 있습니다. 서버 데이터와 클라이언트 상태를 다시 동기화할 필요가 있을 때에만 무효화하면 됩니다.

useMutation 공식문서

profile
즐겁게하자

0개의 댓글

관련 채용 정보