useQuery는 데이터를 읽어오는 용도, useMutation은 데이터를 변경하는 용도이다.
mutationFn: 실행할 API 요청 (POST, PUT, DELETE)
onSuccess: 요청 성공 시 실행할 로직
onError: 요청 실패 시 실행할 로직
const addPost = async (newPost) => {
const { data } = await axios.post(`${api_url}/posts`, newPost);
return data;
};
const mutation = useMutation({
mutationFn: addPost,
onSuccess: (data) => {
console.log("✅ 게시글 추가 성공:", data);
},
onError: (error) => {
console.error("❌ 게시글 추가 실패:", error);
},
});
// 버튼 클릭 시 Mutation 실행
const addPost = () => {
mutation.mutate(newPost);
};
데이터를 추가/수정/삭제 했다면 기존 데이터를 최신 상태로 유지해야 한다.
queryClient.invalidateQueries()
를 사용해 자동으로 쿼리를 재실행해 최신 데이터를 유지한다.
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: addPost,
onSuccess: () => {
queryClient.invalidateQueries({queryKey: ["posts"]}); // 게시글 목록 갱신
},
});
쿼리 무효화: 특정 쿼리의 캐시가 유효하지 않다고 선언하는 것
무효화된 쿼리는 다음에 자동으로 최신 데이터를 가져오게 된다.
서버 응답을 기다리지 않고, UI를 먼저 업데이트하는 걸 옵티미스틱 업데이트라고 한다.
이를 통해 더 나은 UX를 제공할 수 있다. 하지만 관리가 어렵기 때문에 UI 반응성을 높여야 할 곳 일부에 사용하는 것이 좋다.
queryClient.setQueryData: 쿼리의 캐시 데이터를 직접 수정하는 함수 (옵티미스틱 업데이트에 필요)
onMutate: Mutation이 실행되기 직전에 호출되는 함수 (옵티미스틱 업데이트에 필요)
onSettled: Mutation이 성공/실패하든, 요청이 끝나면 실행되는 함수
const mutation = useMutation({
mutationFn: deletePost,
onMutate: async (postId) => {
// 기존 데이터 백업
const prevPosts = queryClient.getQueryData(["posts"]);
// UI에서 삭제된 것처럼 보이게 처리
queryClient.setQueryData(["posts"], (oldPosts) =>
oldPosts.filter((post) => post.id !== postId)
);
return { prevPosts }; // 에러 발생 시 기존 상태로 롤백
},
onError: (error, postId, context) => {
// 기존 상태로 복구
queryClient.setQueryData(["posts"], context.prevPosts);
},
onSettled: () => {
// 서버와 동기화 (쿼리 무효화)
queryClient.invalidateQueries(["posts"]);
},
});
const handleDelete = (postId) => {
mutation.mutate(postId);
};
Mutation 사용 시 상태도 함께 사용하면 더 좋은 UX를 제공할 수 있다.
<script setup>
const updatePost = async ({ id, title }) => {
const { data } = await axios.put(`${api_url}/posts/${id}`, { title });
return data;
};
const mutation = useMutation(updatePost);
const editPost = () => {
mutation.mutate({ id: 1, title: "업데이트된 제목" });
};
</script>
<template>
<div>
<button @click="editPost">게시글 수정</button>
<p v-if="mutation.isLoading">수정 중...</p>
<p v-else-if="mutation.isError">오류 발생: {{ mutation.error.message }}</p>
<p v-else-if="mutation.isSuccess">수정 완료</p>
</div>
</template>
useMutation
을 사용하면 데이터를 추가/수정/삭제 가능
invalidateQueries
를 사용하면 데이터 변경 후 최신 상태 유지 가능
옵티미스틱 업데이트를 적용하면 UI를 빠르게 반영 가능
isLoading
, isError
, isSuccess
를 활용하면 UX 개선 가능
context.previousPosts는 저장해뒀던 값을 다시 설정하는 거니까 이해가 간다. 하지만 oldUsers도 users쿼리의 현재 변경된 값의 이전 값이니까 반환하면 첫번째 방식이랑 같다고 생각했다.
그런데 oldUsers는 변경 전 데이터가 아니라, 이미 변경된 캐시 데이터라고 한다.
onError: (error, postId, context) => {
queryClient.setQueryData(["posts"], context.previousPosts);
},
onError: () => {
queryClient.setQueryData(["users"], (oldUsers: User[]) => {
return oldUsers;
});
},