Optimistic Updates를 할 수 있는 방법은 2가지가 있다.
이 방법은 캐시와 직접적으로 상호작용하지는 않는다.
const { isPending, submittedAt, variables, mutate, isError } = useMutation({
mutationFn: (newTodo: string) => axios.post('/api/data', { text: newTodo }),
// make sure to _return_ the Promise from the query invalidation
// so that the mutation stays in `pending` state until the refetch is finished
onSettled: async () => {
return await queryClient.invalidateQueries({ queryKey: ['todos'] })
},
})
<ul>
{todoQuery.items.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
{isPending && <li style={{ opacity: 0.5 }}>{variables}</li>}
</ul>
위의 예제에서 addTodoMutation.variables 사용하면 쿼리가 렌더링 될 때 mutation이 pending일 때 추가하려는 todo 아이템을 opacity를 다르게 설정해서 구분한 뒤 미리 보여줄 수 있다. refetch가 성공되면 isPending false가 되고 추가하려는 todoItem은 기존의 아이템과 똑같은 opacity를 가지면서 화면에 렌더링 된다.
만약 mutation에 에러가 생기면 isError 상태를 활용해 확인할 수 있다. 하지만 variabels 자체만으로는 명확하지 않음으로 에러가 발생했을 때 버튼 같은 것을 보여주면서 다시 mutate를 다뤄줄 수 있게 해야한다.
isError && (
<li style={{ color: 'red' }}>
{variables}
<button onClick={() => mutate(variables)}>Retry</button>
</li>
)
// somewhere in your app
const { mutate } = useMutation({
mutationFn: (newTodo: string) => axios.post('/api/data', { text: newTodo }),
onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
mutationKey: ['addTodo'],
})
// access variables somewhere else
const variables = useMutationState<string>({
filters: { mutationKey: ['addTodo'], status: 'pending' },
select: (mutation) => mutation.state.variables,
})
useMutationState훅을 활용해 mutationKey 같이 사용하여 다른 컴포넌트에서 mutation을 실행할 수 있다. useMutationState훅을 사용한 varaiables는 배열행태로 전달되어진다. 왜냐하면 다수의 mutation 동시에 실행될 수 있기 때문이다.
우리가 mutation하기 전에 우리의 상태를 낙관적으로 업데이트 할 때 mutation이 실패해 에러가 발생할 가능성이 있다. 이런 에러상황이 생기면 보통은 optimistic 쿼리를 재요청해 서버 상태가 true가 되게 한다. 일부 상황에서는 optimistic 쿼리를 재요청해도 제대로 동작하지 않을 수 있고 재요청을 할 수 없게 만드는 서버 문제 때문에 mutation에러가 발생할 수 있다. 이런 경우 우리는 롤백을 사용해 업데이트를 할 수 있다.
useMutation({
mutationFn: updateTodo,
// When mutate is called:
onMutate: async (newTodo) => {
// Cancel any outgoing refetches
// (so they don't overwrite our optimistic update)
await queryClient.cancelQueries({ queryKey: ['todos'] })
// Snapshot the previous value
const previousTodos = queryClient.getQueryData(['todos'])
// Optimistically update to the new value
queryClient.setQueryData(['todos'], (old) => [...old, newTodo])
// Return a context object with the snapshotted value
return { previousTodos }
},
// If the mutation fails,
// use the context returned from onMutate to roll back
onError: (err, newTodo, context) => {
queryClient.setQueryData(['todos'], context.previousTodos)
},
// Always refetch after error or success:
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] })
},
})
낙관적인 결과를 표시해야 할 위치가 하나뿐이라면, 변수를 사용하고 UI를 직접 업데이트하는 것이 코드를 덜 작성하고 일반적으로 이해하기 쉽다. 예를 들어, 롤백을 처리할 필요가 전혀 없디.
그러나 화면에 여러 위치에서 업데이트를 알아야 하는 경우, 캐시를 직접 조작하는 것이 자동으로 처리해준다.
https://tanstack.com/query/latest/docs/framework/react/guides/optimistic-updates