const { mutate, isPending, isError, error } = useMutation({
// mutationKey: 전송은 응답 데이터를 캐시 처리하지 않기 때문에 필수로 필요한 것은 아님
mutationFn: createNewEvent,
})
// 여기서 받아오는 mutate함수로 이 함수를 언제 실행할지 지정할 수 있다.
Mutation을 사용하여 Post요청하기
useQuery
는 데이터를 가져올 때 사용useMutation
는 데이터를 전송할 때 사용
useMutation 사용법
useMutation 사용
const { mutate, isPending, isError, error } = useMutation({
// mutationKey: 전송은 응답 데이터를 캐시 처리하지 않기 때문에 필수로 필요한 것은 아님
mutationFn: createNewEvent,
})
// 여기서 받아오는 mutate함수로 이 함수를 언제 실행할지 지정할 수 있다.
mutation 함수
export async function createNewEvent(eventData) {
const response = await fetch(`http://localhost:3000/events`, {
methode: 'POST',
body: JSON.stringify(eventData),
headers: {
'Content-Type': 'application/json'
}
})
if(!response.ok) {
const error = new Error('');
error.code = response.status;
error.info = await response.json();
throw error;
}
const { event } = await response.json();
return event;
}
데이터 전송
function handleSubmit(formData) {
mutate({ event: formData })
}
mutate 성공 시 동작 및 쿼리 무효화
요청이 완료되는걸 기다린 후 성공여부에 따른 동작 처리
const { mutate, isPending, isError, error } = useMutation({
mutationFn: createNewEvent,
onSuccess: () => {
navigate('/events')
}
})
전송 완료 후 최신 데이터 받아오기
- 해당 데이터를 불러오는 useQuery에서 이전에 가져온 데이터가 만료됐다고 표시하고 다시 가져오도록 트리거해야함
👉🏽 리액트 쿼리에서 제공하는 메소드를 이용해 하나 이상의 쿼리를 무효화한다.
쿼리 무효화하기
- queryClient로 얻은 객체로 쿼리를 무효화한다.
- 루트파일이 아닌 공통으로 사용하는 파일(여기에서는 api.js파일)로 queryClient를 옮긴 후 export하고, QueryClientProvider에는 import하여 queryClient를 넣어준다.
- useMutation에서 onSuccess할 경우 queryClient를 무효화한다.
👉🏽 현재 화면에 표시된 컴포넌트와 관련된 쿼리가 실행된 경우 특정 데이터가 오래됐으니 만료로 표시하고 즉시 다시가져와야한다고 리액트쿼리에 알려줌const { mutate, isPending, isError, error } = useMutation({ mutationFn: createNewEvent, onSuccess: () => { queryClient.invalidateQueries({queryKey: ['events']}); // ⭐️쿼리 무효화 // 배열의 key가 포함된 모든 쿼리를 무효화한다. navigate('/events') } })
queryClient.invalidateQueries({queryKey: ['events'], exact: true})
exact: true 설정을 넣으면, 쿼리키가 정확히 일치하는 쿼리만 무효화된다.
무효화 후 자동 다시 가져오기 비활성화
예를 들어, 특정 게시글의 상세페이지에서 '삭제'버튼을 클릭하여 해당 게시글을 삭제했을 경우, 쿼리 무효화가 되어있으면 그 게시글을 새로 불러오도록 트리거가 되고 404에러가 발생한다.
이를 막기 위해 쿼리 무효화 후 자동 트리거가 되지 않도록 막을 수 있다.
const { mutate,
isPending: isPendingDeletion,
isError: isErrorDeletion,
error: deleteError
} = useMutation({
mutationFn: deleteEvent,
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['events'],
refetchType: 'none', // ⭐️invalidateQueries를 호출할 때 기존 쿼리가 자동으로 트리거되지 않도록 함. 다음번 요청될 때 다시 실행됨
});
navigate('/events')
}
})
mutate를 사용하여 데이터 업데이트 (PUT 요청하기)
수정화면에서 기존 상세 데이터 가져오기
아래 쿼리는 상세보기 화면에서 사용한 쿼리와 queryKey가 완전히 동일하기 때문에 캐싱된 데이터를 사용하게 되어 데이터를 즉각적으로 표시할 수 있다.
const { data, isPending, isError, error } = userQuery({
queryKey: ['events', params.id],
queryFn: ({signal}) => fetchEvent({ signal, id: params.id }),
update 함수
export async function updateEvent({ id, event }) {
const response = await fetch(`http://localhost:3000/events/${id}`, {
methode: 'PUT',
body: JSON.stringify({ event }),
headers: {
'Content-Type': 'application/json'
}
});
if(!response.ok) {
const error = new Error('');
error.code = response.status;
error.info = await response.json();
throw error;
}
const { event } = await response.json();
return event;
}
mutation
const { mutate, isPending, isError, deleteError } = useMutation({
mutationFn: updateEvent,
// onSuccess: () => {
// queryClient.invalidateQueries({
// queryKey: ['events'],
// });
// navigate(`/details/${id}`)
// }
// ⭐️ onSuccess는 사용하지 않음! #낙관적 업데이트 참조
})
mutate 실행
function handleSubmit(formData) {
mutate({ id: params.id, event: formData })
navigate('../'); //이전 페이지(상세 페이지)로 이동
}
function handleClose(){
navigate('../');
}
낙관적 업데이트
- 특정 데이터를 업데이트할 경우, 업데이트 내용이 서버로부터 성공적으로 업데이트 되기 전에 UI에 즉각적으로 반영하는 것으로, 백엔드의 응답을 기다리지 않아 사용자에게 빠른 피드백을 제공할 수 있는 것이 낙관적 업데이트이다.
- 낙관적 업데이트는 요청이 실패하면 실행된 업데이트를 롤백하는 로직을 포함해야 한다.
- 이는 react query로 비교적 구현하기 쉬운 패턴이자 접근방식이다.
onMutate 옵션 사용
- onMutate의 함수는 mutate를 호출하는 즉시 실행된다. (mutate의 프로세스가 완료되어 응답 받기 전에 실행됨)
- onMutate에서 query client를 통해 리액트 쿼리에 의해 캐시된 데이터를 업데이트한다. setQueryData()
(* query client를 통해 리액트 쿼리와 상호작용하며 쿼리무효화를 지시하거나 캐시된 데이터 변경을 지시할 수 있다) - onMutate는 mutationFn에 연결된 함수의 파라미터를 인자로 전달 받는다.
1. setQueryData() - 캐시 데이터 수정
- 응답을 기다리지 않고 값을 내부적으로 수정하게됨
- 첫번째 파라미터는 쿼리키, 두번째 파라미터는 새로운 데이터이다.
const { mutate, isPending, isError, deleteError } = useMutation({
mutationFn: updateEvent,
onMutate: (data) => {
const newEvent = data.event
queryClient.setQueryData(['events', params.id], newEvent); // ⭐️
}
})
function handleSubmit(formData) {
mutate({ id: params.id, event: formData })
navigate('../'); //이전 페이지(상세 페이지)로 이동
}
2. cancelQueries() - 쿼리 취소
- 특정 키의 모든 활성 쿼리를 취소한다.
- 쿼리를 취소하려는 쿼리키를 전달한다.
- mutation을 취소하는 것이 아니라 useQuery로 트리거된 쿼리만 취소한다.
👉🏽 해당 쿼리의 응답데이터와 낙관적으로 업데이트된 쿼리 데이터가 충돌하지 않게 됨
const { mutate, isPending, isError, deleteError } = useMutation({
mutationFn: updateEvent,
onMutate: async (data) => {
const newEvent = data.event
await queryClient.cancelQueries({queryKey: ['events', params.id]}) // ⭐️
queryClient.setQueryData(['events', params.id], newEvent);
}
})
3. 롤백 처리
- 업데이트 실패시 낙관적 업데이트 롤백 처리
- 업데이트 이전 데이터 보관해두기
const { mutate, isPending, isError, deleteError } = useMutation({
mutationFn: updateEvent,
onMutate: async (data) => {
const newEvent = data.event
await queryClient.cancelQueries({queryKey: ['events', params.id]})
const previosEvent = queryClient.getQueryData(['events', params.id]); // ⭐️ 업데이트 이전 데이터 가져오기
queryClient.setQueryData(['events', params.id], newEvent);
return { previosEvent } // ⭐️ 여기서 return 하는 객체가 context가 됨
},
onError: (error, data, context) => {
// 에러일 경우 롤백
// ⭐️ context에 previosEvent가 포함
queryClient.setQueryData(['events', params.id], context.previosEvent);
}
})
4. 데이터 최신화 유지
onSettled
옵션은 성공 여부와 상관없이 mutation이 완료될 때마다 호출된다.
👉🏽 mutation의 성공, 실패 여부 상관없이 항상 쿼리 무효화를 하여 최신 데이터를 유지한다.
👉🏽 낙관적 업데이트에 실패하여 롤백하더라도 이 mutation이 완료될 때마다 백엔드에서 최신 데이터를 가져왔는지 확인할 수 있다.const { mutate, isPending, isError, deleteError } = useMutation({ mutationFn: updateEvent, onMutate: async (data) => { ... return { previosEvent } }, onError: (error, data, context) => { queryClient.setQueryData(['events', params.id], context.previosEvent); }, onSettled: () => { // ⭐️ 쿼리 무효화로 최신 데이터 유지 queryClient.invalidateQueries(['events', params.id]) } })
쿼리 키를 쿼리 함수 입력으로 사용
아래 쿼리에서는 쿼리키와 쿼리함수에서 동일한 id값을 사용하고 있다.const { data, isPending, isError, error } = userQuery({ queryKey: ['events', {id: params.id}], queryFn: ({signal}) => fetchEvent({ signal, id: params.id }),
게다가 queryFn에서 기본적으로 전달하는 객체에서는 queryKey도 포함하고 있다.
👉🏽 queryKey의 id값을 queryFn에서 재사용하면 됨
const { data, isPending, isError, error } = userQuery({
queryKey: ['events', {id: params.id}],
queryFn: ({signal, queryKey}) => fetchEvent({ signal, ...queryKey[1] }),