저번은 ReactQuery의 useQuery에 대해 포스팅하였다.
이번 프로젝트에서는 get을 제외한 모든 메소드에서도 데이터 패치 관리를 해보자고 해서 SWR이 아닌, ReactQuery를 이용하여 데이터 패치를 하기로 하였다.
useMutation
서버의 데이터를 업데이트 하는 경우에 사용하는 hook
데이터를 가져오기만 하는 GET메소드는 useQuery를 사용한다.
공식문서를 보면 다음과 같이 나와있다.
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(variables, {
onError,
onSettled,
onSuccess,
})
useMutation 첫번째 매개변수는 fetcher함수이며 필수이다.
mutate란?
useMutation의 함수를 실행시킬 수 있으며,
첫번째 인자는 fetcher함수가 받는 인자이며, onError, onSettled, onSuccess를 받을 수 있다.
onError
- fetcher함수를 실패할 때 실행하는 함수
onSetteled
- fetcher함수를 성공하든 실패이든 무조건 실행하는 함수
onSuccess
- fetcher함수를 성공할 때 실행하는 함수 작성
여기서 mutateAsync는 mutate의 결과를 포함하는 Promise를 반환한다.
mutate와 똑같고, 반환하는 것이 다르므로 참고하자!
예시 코드는 다음과 같다.
const { mutate } = useMutation(addTodo)
mutate('todo', {
onSuccess: data => {
console.log(data) //성공시 데이터
},
onError: error => {
console.error(error) //실패시 데이터
},
onSettled: () => {
console.log('settled') // 성공이든 실패이든 무조건!
},
})
queryClient.invalidateQueries([])
- 성공하면 해당 쿼리의 캐시데이터를 제거한다.
- 즉, 해당 쿼리를 서버에서 다시 불러온다.
queryClient.setQueryData([])
- 해당 쿼리를 새롭게 업데이트 하는 함수
프로젝트에 적용해보기!
나는 좋아요 기능의 post, delete를 이용하여 좋아요의 색 변경도 바로바로 되도록 하고 싶었다.
나는 api와 hook을 분리하여 코드를 작성하였다.
// api/List.js
export const postLike = async (itemId) => {
try {
await Axios.post(`/items/${itemId}/like`);
} catch (e) {
if (e.response.status === 400) {
await dislike(itemId);
}
}
};
// hooks/useLike.js
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { postLike } from '../api/List';
const useLike = (sort, id, itemId) => {
const queryClient = useQueryClient();
return useMutation(
() => {
postLike(itemId);
},
{
onSuccess: () => {
return queryClient.invalidateQueries([
`/items?categoryId=${id}&skip=0&limit=100&orderBy=${sort}:dsc`,
]);
},
},
);
};
export default useLike;
// components/Like.js
const Like = () => {
const postLike = useLike(sort, id, itemId);
const like = () => {
postLike.mutateAsync();
};
return (
...
<Like onClick={() => {
setItemId(item._id)
like(item._id);
}}
/>
...
);
}
하지만, 한 번씩 늦게 동작하는 문제가 생겼다.
즉, invalidateQueries가 비동기적이었기 때문이다.
그래서 나는 async await를 이용하였다.
// components/List.js
const postLike = useLike(sort, id, itemId);
const List = () => {
const like = () => {
postLike.mutateAsync();
};
return (
...
<Like onClick={ async () => {
await setItemId(item._id)
await like(item._id);
}}
/>
...
);
}
위 코드처럼 하였더니, 잘 동작이 되었지만,
onClick에서 async await를 쓰는 것은 아니다 라는 말을 들어, 코드를 수정해보기로 하였다.
그래서 생각한 것은 itemId가 바뀔 때 useEffect안의 함수를 실행하는 것이었지만, 원래 axios로 찌르면 onClick함수에서 setState나 useEffect외에 필요한 것이 없는데 굳이? 라는 코드를 계속 쓰게 되어, useMutation의 공식문서를 다시 읽기로 하였다.
mutate는 useMutation을 조작할 수 있는 속성으로, 첫번째 매개변수에는 함수 안에 들어갈 인자를 받을 수 있었다.
즉, 처음에 useMutation의 인자에 함수를 실행할 인자는 필요 없던 것이다!
최종 코드는 다음과 같다.
// hooks/useLike.js
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { postLike } from '../api/List';
const useLike = (sort, id) => {
const queryClient = useQueryClient();
return useMutation(
(itemId) => {
postLike(itemId);
}
);
};
export default useLike;
// components/List.js
const postLike = useLike(sort, id);
const like = (itemId) => {
postLike.mutateAsync(itemId);
queryClient.invalidateQueries([
`/items?categoryId=${id}&skip=0&limit=100&orderBy=${sort}:dsc`,
]);
};
나는 like함수가 실행될 때 mutateAsync를 이용하여 fetcher 함수를 실행하였고, invalidateQueries를 이용하여 해당 쿼리를 무효화해서 서버에 다시 찌를 수 있도록 수정하였다.