React Query는 서버 상태를 관리하는 라이브러리로, 비동기 작업을 쉽게 다룰 수 있도록 도와줍니다. 그중에서 useMutation 훅은 데이터를 생성, 수정, 삭제하는데 사용됩니다.
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;
서버에 요청을 보내는 함수입니다. 비동기 함수로 데이터를 처리합니다.
const postData = async (data: { name: string }) => {
const response = await fetch("/api/data", {
method: "POST",
body: JSON.stringify(data),
});
return response.json();
};
mutationFn이 실행되기 전에 호출되는 콜백 함수입니다. 변수가 전달되며, 여기에서 작업을 미리 처리할 수 있습니다.
onMutate: async (variables) => {
console.log("mutation start: ", variables);
},
mutationFn이 성공적으로 실행된 후 호출되는 콜백 함수입니다. 서버에서 데이터를 정상적으로 받아왔을 때 처리할 작업을 여기에 작성할 수 있습니다.
onSuccess: (data) => {
console.log("success: ", data);
},
mutationFn이 에러를 반환했을 때 호출되는 콜백 함수입니다. 에러 처리를 여기서 할 수 있습니다.
onError: (error) => {
console.error("error occured", error);
},
mutationFn이 완료된 후, 성공과 실패에 관계없이 항상 호출되는 콜백 함수입니다. 주로 캐시 무효화나 후처리를 할 때 유용합니다.
onSettled: () => {
console.log("mutation completed");
queryClient.invalidateQueries({ queryKey: ["data"] });
},
4가지 상태를 가지고 있습니다.
mutation.status: "error" | "idle" | =pending" | "success"
초기상태로, Mutation이 아직 실행되지 않은 상태입니다.
{mutation.isIdle && <p>초기 상태</p>}
Mutation이 진행 중인 상태입니다. 비동기 함수가 호출되고, 결과를 기다리는 동안 표시됩니다.
{mutation.isPending && <p>요청 진행 중</p>}
mutationFn이 성공적으로 실행된 상태입니다.
{mutation.isSuccess && <p>성공적으로 요청 완료</p>}
mutationFn이 실행 중 에러가 발생한 상태입니다.
{mutation.isError && <p>요청 중 에러 발생</p>}
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;
onMutate에서 캐시 업데이트를 미리 진행합니다. 이때 previousData를 return 해서, 실패했을 때 원래 상태로 되돌릴 수 있도록 합니다. context는 onMutate에서 반환된 데이터를 onError와 onSuccess에서 사용할 수 있도록 전달하는 역할을 합니다.
onError에서는 onMutate에서 반환된 previous를 사용하여 캐시를 원래 상태로 복구합니다. 이 과정에서 캐시가 업데이트되기 전에 사용했던 데이터로 되돌립니다.
성공적인 응답 후에는 서버에서의 변경을 반영하기 위해 캐시를 무효화합니다.
케바케로 생각됩니다. 상황에 맞게 사용합시다.
onMutate에서 캐시를 미리 업데이트 했다면, 서버 응답을 기다리지 않고 UI에서 바로 변경된 데이터를 볼 수 있게 되므로, 무효화를 하고 다시 가져오는 것이 불필요할 수 있습니다. 서버 데이터와 클라이언트 상태를 다시 동기화할 필요가 있을 때에만 무효화하면 됩니다.