Mutation은 서버에 데이터를 업데이트 하도록 서버에 네트워크 호출을 실시한다
CRUD(Create, Read, Update, Delete)에서
useQuery가 C를 맡았다면, useMutation은 RUD를 담당한다
Mutation은 변경 내용을 사용자에게 보여주거나 진행된 변경 내용을 등록하여 사용자가 볼 수 있게 하는 방법들이 있다
하나는 Optimistic UI이다
Optimistic UI는 서버 호출에서 모든 내용이 잘 진행될 것으로 간주하는 것이다
그리고 사실이 아닌 것으로 판명될 경우 롤백을 진행한다
예를들어 페이스북의 좋아요 기능을 보자
사용자가 좋아요를 누르면 서버에 좋아요 데이터가 업데이트 되고 완료된다면 버튼 색이 변할 것이다
하지만 Optimistic UI를 이용하면 좋아요를 누르면 서버 전송이 잘 될 것이라고 간주하여 즉시 버튼 색을 변화시킨다. 만약 좋아요 기능에 에러가 났다면 다시 롤백이 될 것이다
또 다른 방법은 Mutation 호출을 하면 서버에서 받는 데이터를 취하고 업데이트된 해당 데이터로 React Query 캐시를 업데이트 하는 것이다
마지막으로 관련 쿼리를 무효화(invalidation)할 수 있다
무효화하는 경우 서버에서 리페치를 개시하여 클라이언트에 있는 데이터를 서버의 데이터와 최신상태로 유지하게 된다
useMutation은 일부 예외를 제외하고 useQuery와 상당히 유사하다
mutate함수를 반환받는데 이 mutatie함수는 우리가 변경 사항을 토대로 서버를 호출할 때 사용할 것이다
데이터를 저장하지 않으므로 쿼리키는 필요로 하지 않는다
마찬가지로 변이에 관련된 캐시는 존재하지 않기 때문에 isLoading은 존재하지만 isFetching은 없다
재시도 또한 기본값으로 존재하지 않는다 (옵션 설정 가능)
// useMutation
const {
data,
error,
isError,
isIdle,
isLoading,
isPaused,
isSuccess,
mutate,
mutateAsync,
reset,
status,
} = useMutation(mutationFn, {
cacheTime,
mutationKey,
networkMode,
onError,
onMutate,
onSettled,
onSuccess,
retry,
retryDelay,
useErrorBoundary,
meta
})
// mutate함수
mutate(variables, {
onError,
onSettled,
onSuccess,
})
useMutation은 인자로 쿼리키를 받지않고 mutationFn를 받는다
mutationFn에서는 useQuery훅의 QueryFn과 다르게 인자를 제공할 수 있다
const deleteMutation = useMutation((postId: number) => deletePost(postId));
인자로 받는 postId는 mutate함수의 variables로 받는다
<button onClick={() => deleteMutation.mutate(post.id)}>Delete</button>
지난 블로그 포스트 실습에 이어서 게시글을 삭제하고 업데이트하는 기능을 useMutation훅을 사용해서 만들어보자
import { useMutation, useQuery } from "@tanstack/react-query";
import { IPost } from "./Posts";
interface IPostDetailProps {
post: IPost;
}
interface IComments {
id: number;
email: string;
body: string;
}
async function fetchComments(postId: number) {
const response = await fetch(
`https://jsonplaceholder.typicode.com/comments?postId=${postId}`
);
return response.json();
}
async function deletePost(postId: number) {
const response = await fetch(
`https://jsonplaceholder.typicode.com/postId/${postId}`,
{ method: "DELETE" }
);
return response.json();
}
async function updatePost(postId: number) {
const response = await fetch(
`https://jsonplaceholder.typicode.com/postId/${postId}`,
{
method: "PATCH",
body: JSON.stringify({ title: "REACT QUERY FOREVER!!!!" }),
}
);
return response.json();
}
export function PostDetail({ post }: IPostDetailProps) {
// replace with useQuery
const { data, isLoading, isError } = useQuery<IComments[]>(
["comments", post.id],
() => fetchComments(post.id)
);
const deleteMutation = useMutation((postId: number) => deletePost(postId));
const updateMutation = useMutation((postId: number) => updatePost(postId));
return (
<>
<h3 style={{ color: "blue" }}>{post.title}</h3>
<button onClick={() => deleteMutation.mutate(post.id)}>
Delete
</button>{" "}
<button onClick={() => updateMutation.mutate(post.id)}>
Update title
</button>
{deleteMutation.isError && <div style={{ color: "red" }}>ERROR</div>}
{deleteMutation.isLoading && (
<div style={{ color: "blue" }}>deleting post...</div>
)}
{deleteMutation.isSuccess && (
<div style={{ color: "green" }}>success delete post</div>
)}
{updateMutation.isError && <div style={{ color: "red" }}>ERROR</div>}
{updateMutation.isLoading && (
<div style={{ color: "blue" }}>updating post...</div>
)}
{updateMutation.isSuccess && (
<div style={{ color: "green" }}>success update post</div>
)}
<p>{post.body}</p>
<h4>Comments</h4>
{isError && <div>ERROR!</div>}
{isLoading ? (
<div>Loading...</div>
) : (
data?.map((comment) => (
<li key={comment.id}>
{comment.email}: {comment.body}
</li>
))
)}
</>
);
}
JSONPlaceholder의 api를 사용했기 때문에 실제로 값이 삭제되거나 변경되지는 않는다
delete버튼과 update버튼이 각각 클릭됐을때 mutation함수가 실행된다
update의 경우 실제로는 업데이트 될 값을 직접 입력받겠지만 예시이기 때문에 값을 하드코딩하여 업데이트 하도록 했다
mutation의 isError, isLoading, isSuccess를 사용해서 mutation 실행 과정과 결과를 알 수 있다
감사합니다. 잘 보고 갑니다.