낙관적 업데이트할 때 쿼리 요청을 취소하는 이유(Tanstack Query Optimistic Updates)

우혁·2024년 8월 11일

Tanstack Query

목록 보기
3/3

낙관적 업데이트(Optimistic Updates)란?

사용자가 데이터를 수정할 때, 서버와의 통신 결과를 기다리지 않고 즉시 사용자 인터페이스(UI)를 업데이트하는 기법이다. 이 방식은 사용자 경험(UX)을 향상시키고, 애플리케이션의 반응성을 높이는 데 도움을 준다.

낙관적 업데이트를 사용하지 않았을 때 플로우

낙관적 업데이트를 사용했을 때 플로우

  • 서버로부터 성공 응답을 받은 경우
  • 서버로부터 에러 응답을 받은 경우

사용 사례

  • 소셜 미디어: 댓글 추가, 좋아요 클릭 등에서 즉시 UI를 반영하여, 사용자 경험을 향상시킨다.

  • 할 일 관리 애플리케이션: 사용자가 할 일을 추가하거나 삭제할 때, 즉시 목록에 반영한다.

  • 전자상거래: 장바구니에 상품을 추가할 때, 사용자에게 즉시 업데이트된 장바구니 상태를 보여준다


Tanstack Query에서 Optimistic Updates 사용하기

Tanstack Query(React Query)는 뮤테이션이 완료되기 전에 UI를 낙관적으로 업데이트하는 두 가지 방법을 제공한다.

1. UI를 통해(Via the UI)

const addTodoMutation = useMutation({
  mutationFn: (newTodo: string) => axios.post('/api/data', { text: newTodo }),
  onSettled: async () => { // promise가 완료될 때까지 Pending 상태
    return await queryClient.invalidateQueries({ queryKey: ['todos'] })
  },
})

const { isPending, submittedAt, variables, mutate, isError } = addTodoMutation

<ul>
  {todoQuery.items.map((todo) => (
    <li key={todo.id}>{todo.text}</li>
  ))}
  {isPending && <li style={{ opacity: 0.5 }}>{variables}</li>}
</ul>
  • onSettled를 async 함수로 선언하여 Promise가 완료될 때까지 pending 상태를 유지하고 UI는 사용자에게 처리 중임을 알린다.

  • pending 상태일 때는 추가될 할 일 목록을 투명도 0.5로 하여 렌더링한다.

  • pending 상태가 끝났다는건 쿼리 무효화가 완료됨을 의미하니깐 서버와 동기화가 되어 추가한 할 일이 목록에 추가된다.

  • 만약 에러가 발생해도 할 일 목록에 추가 되지 않아서 문제가 되지 않는다!

💡 submittedAt, variables 이란?
submittedAt: 뮤테이션 요청이 제출된 시간(타임스탬프)를 나타내는 속성이다.
variables: 뮤테이션 요청을 수행할 때 전달된 인자(변수)를 나타내는 속성이다.


2. 캐시를 통해(Via the Cache)

const queryClient = useQueryClient()

useMutation({
  mutationFn: updateTodo,
  onMutate: async (newTodo) => {
    await queryClient.cancelQueries({ queryKey: ['todos'] })
    
    const previousTodos = queryClient.getQueryData(['todos'])
    queryClient.setQueryData(['todos'], (old) => [...old, newTodo])
    
    return { previousTodos }
  },
  onError: (err, newTodo, context) => {
    queryClient.setQueryData(['todos'], context.previousTodos)
  },
  onSettled: () => {
    queryClient.invalidateQueries({ queryKey: ['todos'] })
  },
})
  • 현재 진행 중인 쿼리 요청 취소하기

  • 현재 쿼리 캐시에서 todos 데이터 가져와서 previousTodos 변수에 저장(에러 발생 시 롤백할 때 사용)

  • 쿼리 캐시를 낙관적 업데이트하여 새로운 할 일 추가하기

  • onMutate에서 반환된 객체는 onError에서 사용할 수 있는 컨텍스트를 제공한다.

  • 만약 에러가 발생한다면 반환된 컨텍스트(previousTodos)를 사용하여 롤백

  • mutate가 끝나면 쿼리 키를 무효화여 서버 동기화


onMutate에서 쿼리 요청을 취소하는 이유

나는 이 코드만 봤을 때는 쿼리 요청을 취소해야 할 이유가 없다고 생각해서 찾아보니깐 지금처럼 요청이 하나인 경우가 아니라 여러 개의 요청이 발생했을 경우에 이미 낙관적 업데이트가 된 UI가 덮어쓰일 수 있기 때문에 쿼리 요청을 취소하는 거였다.

예를 들어 할 일 목록에서 할 일3 을 완료로 업데이트하고 할 일 4를 추가하는 상황을 생각해보면

1. 할 일 3을 업데이트한다. ➔ 할 일 3을 완료로 변경하는 요청을 서버로 보낸다

2. 할 일 4를 추가(낙관적 업데이트)한다. ➔ 할 일 4를 추가하는 요청을 서버로 보내고, UI는 즉시 할 일 4를 추가한 상태로 업데이트 한다.

3. 할 일 3이 업데이트되어 변경 사항을 반영하기 위해 쿼리 무효화(refetch)가 발생한다.

4. 변경 사항이 업데이트 된다. ➔ 기존 낙관적 업데이트로 할 일 4를 추가했는데 업데이트로 인해 사라질 수 있다.

5. 할 일 4를 추가하는 뮤테이트가 완료된 후 변경 사항이 업데이트되어 할 일 4가 다시 보이게 된다.

💡 이렇게 쿼리 무효화(refetch)를 통해 업데이트가 발생하는 경우에 기존 낙관적 업데이트로 추가된 항목이 서버 데이터와 일치하지 않을 경우 UI에서 사라질 수 있기 때문에 낙관적 업데이트의 장점이 사라지게 되어 쿼리 요청을 취소하는 것이다!


UI를 통해(Via the UI) vs 캐시를 통해(Via the Cache)

특징

  • UI: UI에서 직접적으로 상태를 관리하여 사용자가 요청한 변경 사항을 즉시 반영한다.

  • Cache: 서버에 요청을 보내기 전에 먼저 로컬 캐시를 즉시 업데이트한다. 이는 사용자가 요청한 변경 사항을 즉시 반영하는 방식이다.

장점

  • UI: 서버에서 응답이 성공적으로 돌아오면 그에 맞춰 UI 상태를 업데이트하므로, 데이터의 일관성이 유지된다.

  • Cache: 사용자가 요청을 보낸 즉시 UI에 변화가 반영되므로, 사용자 경험이 향상된다.

단점

  • UI: 별도의 상태 관리 로직을 구현해야 하므로 복잡성이 증가할 수 있다.

  • Cache: 서버에서 응답이 실패하거나 데이터가 변경되면, UI와 서버 데이터 간의 불일치가 발생할 수 있다.

사용 상황

  • UI: 데이터의 일관성이 매우 중요한 경우(ex: 결제 시스템, 사용자 프로필 업데이트 등)

  • Cache: 사용자가 즉시 결과를 확인해야 하는 경우(ex: 할 일 추가, 삭제 등)

📝 정리하기

UI를 통한(Via the UI) 낙관적 업데이트는 데이터의 일관성을 유지하는데 유리하지만, 사용자 경험에 약간의 지연이 발생할 수 있다.

캐시를 통한(Via the Cache) 낙관적 업데이트는 즉각적인 반응성을 제공하지만, 데이터의 일관성 문제와 오류 처리의 복잡성을 동반한다.

즉각적인 사용자 피드백이 필요한 경우 ➔ 캐시를 통한 방법

데이터의 일관성이 중요한 경우 ➔ UI를 통한 방법


🙃 도움이 되었던 자료들

Optimistic Updates in Tanstack Query v5
Tanstack Query 공식문서 - Optimistic Updates
예시로 살펴보는 낙관적 업데이트(optimistic updates) 2가지 방법
react-query optimistic update시 데이터 꼬임 방지
React-Query를 사용하여 optimistic update 구현하기

profile
🏁

2개의 댓글

comment-user-thumbnail
2025년 2월 16일

잘 읽고 갑니다! 출처 표기까지 해주셔서 편하네요 :) 좋은하루 되세요!

1개의 답글