useMutation, Optimistic Update 패턴
useMutation 훅은 비동기 작업(특히 서버에 데이터를 추가, 수정, 삭제하는 작업)을 처리할 때 사용됩니다.
이 예제에서는 addTodo 함수가 서버에 새로운 할 일을 추가하는 비동기 작업을 수행합니다.useMutation 훅을 사용하여 이러한 작업의 상태(성, 실패, 진행 중 등)를 관리하고, 해당 작업이 애플리케이션의 상태에 미치는 영향을 제어할 수 있습니다.
주요 콜백 및 설정
- mutationFn:
mutationFn은 실제로 변이를 수행하는 비동기 함수입니다. 여기서는addTodo함수가 이 역할을 합니다.
- onMutate:
onMutate는 변이가 시작될 때 호출됩니다.- 서버에 요청을 보내기 전에 로컬 상태를 업데이트하여 응답 지연을 사용자에게 숨기기 위한 낙관적 업데이트(Optimistic Update)를 수행합니다.
- 쿼리를 취소하여 경쟁 상태를 방지하고, 현재의
todos상태를 가져와 이전 상태로 되돌릴 수 있도록 저장합니다. queryClient.setQueryData를 사용하여 로컬 상태를 즉시 업데이트합니다.- 반환된 객체(
previousTodos)는onError또는onSettled에서 사용할 수 있습니다.
- onError:
- 변이가 실패했을 때 호출됩니다.
- 낙관적 업데이트로 인해 변경된 로컬 상태를 이전 상태로 복원합니다.
context매개변수는onMutate에서 반환된 값을 포함합니다.
- onSettled:
- 변이가 성공하든 실패하든 상관없이 변이가 끝나면 호출됩니다.
invalidateQueries를 사용하여todos쿼리를 무효화하여, 서버에서 최신 데이터를 다시 가져오도록 합니다.
예시 코드
const addMutation = useMutation({
mutationFn: addTodo, // 실제 변이 함수
onMutate: async (newTodo) => {
console.log("onMutate 호출");
await queryClient.cancelQueries({ queryKey: ["todos"] }); // 쿼리 취소
const previousTodos = queryClient.getQueryData(["todos"]); // 현재 상태 저장
queryClient.setQueryData(["todos"], (old) => [...old, newTodo]); // 낙관적 업뎃
return { previousTodos }; // 이전 상태 반환
},
onError: (err, newTodo, context) => {
console.log("onError");
console.log("context:", context);
queryClient.setQueryData(["todos"], context.previousTodos); // 오류 시 이전 상태 복원
},
onSettled: () => {
console.log("onSettled");
queryClient.invalidateQueries({ queryKey: ["todos"] }); // 변이 후 쿼리 무효화
},
});
각 단계의 의미
- onMutate:
- 변이가 시작될 때 호출되며, 이 시점에서 쿼리를 취소하여 현재 요청이 충돌하지 않도록 합니다.
- 현재
todos데이터를 가져와 저장한 후, 낙관적 업데이트를 수행하여 UI를 즉시 업데이트합니다. - 낙관적 업데이트는 사용자 경험을 향상시킵니다. 서버 응답을 기다리는 동안 사용자에게 즉각적인 피드백을 제공합니다.
- onError:
- 서버에 변이 요청이 실패하면 호출됩니다.
- 낙관적 업데이트로 변경된 상태를 원래 상태로 되돌립니다.
context매개변수를 통해onMutate에서 반환된previousTodos를 사용합니다.
- onSettled:
- 변이가 성공하든 실패하든 무조건 호출됩니다.
- 쿼리를 무효화하여 서버에서 최신 데이터를 다시 가져오도록 합니다.
전체 프로세스 요약
- 변이 시작:
onMutate가 호출되어 쿼리를 취소하고 낙관적 업데이트를 수행합니다. - 변이 중: 서버에 실제로 데이터를 추가합니다(
mutationFn). - 변이 성공/실패: 변이 성공 시
onSettled가 호출되고, 실패 시onError가 호출됩니다. 두 경우 모두onSettled에서 쿼리를 무효화하여 최신 데이터를 가져옵니다.
이렇게 하면 서버와의 상호작용에서 더 나은 사용자 경험을 제공하고, 상태 관리가 간편해집니다.
setQueryData의 시점
이렇게 하면 previousTodos에 newTodo가 추가되지 않습니다.previousTodos는 setQueryData를 호출하기 전에 현재 상태를 저장한 것이기 때문입니다. queryClient.getQueryData(["todos"])는 현재 쿼리의 데이터를 반환하며, 이는 setQueryData를 호출하기 전에 실행되므로 newTodo가 추가되기 전의 상태입니다.
이것은 previousTodos가 setQueryData 호출 이후 상태 변경에 영향을 받지 않는 이유를 설명합니다. previousTodos는 onError 핸들러에서 상태를 복원하는 데 사용됩니다.
자세한 설명
- 현재 상태 저장:
- 이 줄은
setQueryData를 호출하기 전에 현재todos상태를previousTodos변수에 저장합니다. - 이 시점에서
previousTodos에는newTodo가 포함되지 않습니다.
- 이 줄은
const previousTodos = queryClient.getQueryData(["todos"]);- 낙관적 업데이트:
- 이 줄은
todos쿼리 데이터를 즉시 업데이트합니다.old는 현재 상태를 나타내며,newTodo를 추가하여 새로운 상태를 생성합니다. - 이 업데이트는
queryClient의 내부 캐시에만 영향을 미칩니다.
- 이 줄은
queryClient.setQueryData(["todos"], (old) => [...old, newTodo]);- 이전 상태 반환:
- 이 줄은
onMutate콜백에서previousTodos를 반환합니다. previousTodos는onMutate가 호출된 시점에서의 상태를 나타내며, 이는newTodo가 추가되기 전의 상태입니다.
- 이 줄은
return { previousTodos };
예제 코드
const addMutation = useMutation({
mutationFn: addTodo,
onMutate: async (newTodo) => {
console.log("onMutate 호출");
await queryClient.cancelQueries({ queryKey: ["todos"] });
// 현재 상태 저장
const previousTodos = queryClient.getQueryData(["todos"]);
// 낙관적 업데이트
queryClient.setQueryData(["todos"], (old) => [...old, newTodo]);
// 이전 상태 반환
return { previousTodos };
},
onError: (err, newTodo, context) => {
console.log("onError");
console.log("context:", context);
// 오류 시 이전 상태 복원
queryClient.setQueryData(["todos"], context.previousTodos);
},
onSettled: () => {
console.log("onSettled");
// 변이 후 쿼리 무효화
queryClient.invalidateQueries({ queryKey: ["todos"] });
},
});
요약
previousTodos는setQueryData호출 전에 저장된 상태이며,newTodo를 포함하지 않습니다.setQueryData는queryClient의 내부 캐시에만 영향을 미칩니다.previousTodos는onError에서 상태를 복원하는 데 사용됩니다.
이렇게 하면 onMutate와 onError 핸들러가 적절하게 동작하고, 낙관적 업데이트를 통해 사용자 경험을 향상시키면서도 오류 시 상태를 원래대로 복원할 수 있습니다.
'library' 카테고리의 다른 글
| [240421 TIL] Zustand 함수형업데이트, immer, persist (0) | 2024.06.21 |
|---|---|
| [240620 TIL] Tanstack Query + Zustand (0) | 2024.06.20 |
| [240615 WIL] queryKey (0) | 2024.06.15 |
| [240614 TIL] TanstackQuery - Life Cycle (0) | 2024.06.14 |
| [240613 TIL] Auth with Tanstack Query (0) | 2024.06.13 |