React-Query에서 mutation의 역할은 서버에 있는 데이터를 업데이트하기 위해 서버로 네트워크 콜을 보내는 것이다.
즉 Create, Update, Delete할 때 쓰인다.
우리는 mutaion을 활용하기 위해 useMutation hook을 사용할 것이다.
CRUD의 사용방법은 다음과 같다.
Read: useQuery
Create, Update, Delete: useMutation
useMutation은 몇 가지 빼고는 useQuery와 비슷하다.
실제 네트워크 콜 할 때, 사용할 mutate function을 리턴
- 우리는 useMutation 리턴 오브젝트 값의 mutate function property를 이용하여 mutation call을 전달할 것이다.
cache에 데이터를 저장하는 것이 아니기 떄문에 query key는 불필요하다.
cache data가 없기 때문에 isFetching은 없고 isLoading만 있다.
cache data가 없기 때문에 refetch하지 않는다.
isFetching과 isLoading의 차이를 알고 싶다면 여기로
default값으로 retry를 하지 않는다. (물론 따로 설정 가능)
useQuery는 default로 3번 retry한다.
onMutate callback함수가 있다. 이는 후에 optimistic update할 때 사용한다.
mutation을 사용하기 전 global fetching indicator와 error handling을 설정해준다.
useIsFetcing
을 사용했는데, 이와 비슷한 useIsMutating
을 사용한다. export const queryClient = new QueryClient({
mutationCache: new MutationCache({
onError: queryErrorHandler
})
})
mutate function
을 리턴받는다.const { mutate } = useMutation((updateData) => mutateFn(updateData))
// updateData를 mutate함수의 인자에 넣어주면 mutateFn이 인자를 받아 서버로 mutate 콜을 보낸다.
mutate(updateData)
invalidateQueries
method를 사용한다.QueryClient에는 invalidateQueries라는 method가 있다. 우리는 변이된 cache data를 무효화하기 위해 이를 사용할 것이다.
Query Invalidation 공식문서
역할
- query에 stale 마크를 남긴다. => 이 데이터는 상했어! 라고 말하는 것
이를 사용하면 유저는 page를 refresh할 필요가 없다!
- 무효화하면 react-query 내에서 이 데이터는 변이된 데이터임을 알아차리고 refetch를 하기 때문이다!
const queryClient = useQueryClient()
const { mutate } = useMutation((updateData) => mutateFn(updateData), {
onSuccess: () => {
queryClient.invalidateQueries('query key')
}
})
useMutation return type으로 UseMutationFunction
을 사용한다.
인자 값은 총 4개이다.
반환하는 데이터가 없다면 void로 설정
사용예시
export function useReserveAppointment(): UseMutateFunction<void, unknown, Appointment, unknown> {
const { user } = useUser();
const toast = useCustomToast();
const queryClient = useQueryClient()
const { mutate } = useMutation((appointment: Appointment) => setAppointmentUser(appointment, user?.id), {
onSuccess: () => {
queryClient.invalidateQueries([queryKeys.appointments]),
toast({
title: "You have reserved the appointment!",
status: "success"
})
}
})
return mutate
}
optimistic update는 말 그대로 낙관적 업데이트를 의미한다.
바뀌는 값을 안다면, 서버에서 잘 작동할 것이라 가정하고 클라이언트 내에서 해당 query key의 cache 데이터를 업데이트한다.
cancelQueies
이용export function usePatchUser(): UseMutateFunction<User, unknown, User, unknown> {
const { user, updateUser } = useUser();
const toast = useCustomToast()
const queryClient = useQueryClient()
const { mutate } = useMutation((newUserData: User) => patchUserOnServer(newUserData, user), {
// onMutate returns context that is passed to onError
onMutate: async (newData: User | null) => {
// 중간에 이전값이 덮어씌워지는 것을 막아준다.
queryClient.cancelQueries(queryKeys.user)
// 실패할 것을 대비하여 이전 값 저장
const previousUserData:User = queryClient.getQueryData(queryKeys.user)
// 업데이트 값을 cache data에 저장
queryClient.setQueryData(queryKeys.user, newData)
// 저장해둔 이전 값을 리턴 => context value를 통해 onError로 전달한다.
return { previousUserData }
},
onError: (error, newData, context) => {
// 저장해둔 값을 context에서 추출한다.
if(context.previousUserData) {
queryClient.setQueryData(queryKeys.user, context.previousUserData)
toast({
title: "Update failed; restoring previous values",
status: "warning"
})
}
},
onSuccess: (userData: User | null) => {
// 성공시 toast 날린다.
if(userData) {
toast({
title: "User updated!",
status: "success"
})
}
},
onSettled: () => {
// 성공했든 실패했든 cache 데이터를 최신값으로 유지하기 위해 무조건 invalidate해준다.
queryClient.invalidateQueries(queryKeys.user)
}
})
return mutate;
}
공식문서와 udemy의 react-query 강의를 통해 시작한 react-query 공부가 끝이 났다.
마지막에 testing 관련 강의가 있었지만 아직 이해를 하지 못해서 velog에는 쓰지 않기로 했다.
정말 유용한 기능들이 많았다. 이를 잘 활용하기 위해 꾸준히 복습하고 내 프로젝트에 적용시켜봐야겠다.