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 |