728x90
728x90

낙관적 업데이트(Optimistic Updates) (React Query)

들어가며

  • 리액트 쿼리(React Query)에서 사용되는 낙관적 업데이트(Optimistic Updates)에 대하여 정리해본다.

 

낙관적 업데이트(Optimistic Updating)

개념

  • 데이터가 실제 서버에 반영되기 전미리 UI에 반영하여 사용자 경험(UX)을 향상시키는 기법
  • 사용자가 서버 응답을 기다리지 않고도 즉각적인 피드백을 받을 수 있게 해준다.

 

주요 단계

① 변경 전 상태 저장

  • 낙관적 업데이트를 수행하기 전에, 현재 상태를 저장한다.
  • 이 작업은 서버 요청이 실패했을 경우 원래 상태로 롤백(Rollback)하기 위해 필요하다.

 

② 낙관적 상태 업데이트

  • 서버에 데이터를 전송하기 전에, 리액트 쿼리의 @queryClient.setQueryData@ 메서드를 사용하여 로컬 상태를 즉시 업데이트한다.
    • 이 작업 전에 @queryClient.cancelQueries@ 메서드를 사용하여 해당 쿼리에 대한 모든 활성 쿼리를 취소해줄 수 있다.
  • 이 작업은 사용자가 변경 사항을 즉시 확인할 수 있도록 한다.

 

③ 서버 요청 수행

  • 서버에 실제 데이터를 전송한다.
  • 이때 @mutate@ 또는 @mutateAsync@ 메서드를 사용한다.

 

④ 성공(또는 실패) 시 상태 반영

  • 서버 요청이 성공하면, 서버로부터 받은 최신 데이터를 다시 상태에 반영한다.
  • 이 작업은 @onSettled@ 콜백 함수에서 수행된다.
    • 성공 시에만 상태를 반영하려면 @onSuccess@ 콜백 함수를 사용한다.

 

⑤ 실패 시 롤백하기

  • 서버 요청이 실패하면, 저장해둔 원래 상태로 롤백한다.
  • 이 작업은 @onError@ 콜백 함수에서 수행된다.

 

사용 예제

① 변경 전 상태 저장
  • @handleSubmit@ 이벤트 핸들러 함수에서 @mutate@ 메서드에 인자로 넘겨준 객체의 @event@ prop의 값을 @useMutation@ 훅의 @onMutate@ 콜백 함수에서 @data@ 값에 접근하여 가져올 수 있다.
import { useMutation } from '@tanstack/react-query';

const { mutate } = useMutation({
  mutationFn: updateEvent,
  onMutate: async (data) => {
    // mutate() 함수에 인자로 전달한 것을 받아서 가져올 수 있다.
    const newEvent = data.event;
  }
}

function handleSubmit(formData) {
  mutate({
    id: params.id,
    event: formData,
  });
  navigate('../');
}

 

② 낙관적 상태 업데이트
  • @queryClient.cancelQueries@ 메서드를 이용하여 해당 쿼리에 대한 활성 쿼리를 취소해준다.
  • 그리고 @queryClient.setQueryData@ 메서드를 사용하여 로컬 상태를 즉시 업데이트 해준다.
  • 그리고 에러 발생 시, 롤백을 하기 위해 이전의 값(@previousEvent@)을 return 해준다.
import { useMutation } from '@tanstack/react-query';

const { mutate } = useMutation({
  mutationFn: updateEvent,
  onMutate: async (data) => {
    // mutate() 함수에 인자로 전달한 것을 받아서 가져올 수 있다.
    const newEvent = data.event;
    
    // 활성 쿼리 취소하기
    await queryClient.cancelQueries({ queryKey: ['events', params.id] });
    
    // 이전의 이벤트 정보 가져오기 (에러 발생 시 롤백하기 위해)
    const previousEvent = queryClient.getQueryData(['events', params.id]);
    
    // context로 반환
    return { previousEvent };
  }
}

function handleSubmit(formData) {
  mutate({
    id: params.id,
    event: formData,
  });
  navigate('../');
}

 

 

③ 서버 요청 수행
  • @mutate@ 함수를 사용하여 서버에 실제 데이터를 전송한다.
function handleSubmit(formData) {
  mutate({
    id: params.id,
    event: formData,
  });
  navigate('../');
}

 

④ 성공(또는 실패) 시 상태 반영
  • @onSettled@ 콜백 함수에 서버 요청 성공/실패와 상관 없이 수행할 작업들을 넣는다.
    • 서버 요청이 성공할 시에만 처리하려면 @onSuccess@ 콜백 함수에 해당 작업을 넣는다. 
  • @queryClient.invalidateQueries@를 사용하여 최신 데이터를 백엔드에서 가져온다.
import { useMutation } from '@tanstack/react-query';

const { mutate } = useMutation({
  mutationFn: updateEvent,
  onMutate: async (data) => {
    // mutate() 함수에 인자로 전달한 것을 받아서 가져올 수 있다.
    const newEvent = data.event;
    
    // 활성 쿼리 취소하기
    await queryClient.cancelQueries({ queryKey: ['events', params.id] });
    
    // 이전의 이벤트 정보 가져오기 (에러 발생 시 롤백하기 위해)
    const previousEvent = queryClient.getQueryData(['events', params.id]);
    
    // context로 반환
    return { previousEvent };
  }
  onSettled: () => {
      // 성공하든 실패하든 처리할 작업
      queryClient.invalidateQueries(['events', params.id]);
    },
  });
}

function handleSubmit(formData) {
  mutate({
    id: params.id,
    event: formData,
  });
  navigate('../');
}

 

⑤ 실패 시 롤백하기
  • 서버 요청이 실패하면 저장해둔 원래 상태로 롤백한다.
  • @onError@ 콜백 함수에 해당 코드를 넣는다.
  • 이때 @onError@ 콜백 함수의 @context@ 매개변수에 접근하여 @onMutate@ 콜백 함수에서 return 했던 @previousEvent@ 값을 가져올 수 있다.
import { useMutation } from '@tanstack/react-query';

const { mutate } = useMutation({
  mutationFn: updateEvent,
  onMutate: async (data) => {
    // mutate() 함수에 인자로 전달한 것을 받아서 가져올 수 있다.
    const newEvent = data.event;
    
    // 활성 쿼리 취소하기
    await queryClient.cancelQueries({ queryKey: ['events', params.id] });
    
    // 이전의 이벤트 정보 가져오기 (에러 발생 시 롤백하기 위해)
    const previousEvent = queryClient.getQueryData(['events', params.id]);
    
    // context로 반환
    return { previousEvent };
  }
  onError: (error, data, context) => {
    queryClient.setQueryData(['events', params.id], context.previousEvent);
  },
  onSettled: () => {
      // 성공하든 실패하든 처리할 작업
      queryClient.invalidateQueries(['events', params.id]);
    },
  });
}

function handleSubmit(formData) {
  mutate({
    id: params.id,
    event: formData,
  });
  navigate('../');
}

 

 

  • 실행 화면은 다음과 같다.

 

참고 사이트

 

Optimistic Updates | TanStack Query React Docs

React Query provides two ways to optimistically update your UI before a mutation has completed. You can either use the onMutate option to update your cache directly, or leverage the returned variables to update your UI from the useMutation result. Via the

tanstack.com

 

728x90
728x90