Tanstack Query 낙관적 업데이트

유의진·2024년 12월 24일
0

찍찍이

목록 보기
2/6
post-thumbnail

낙관적 업데이트란?

사용자가 데이터 변경 작업을 수행했을 때, 실제 서버 응답을 기다리지 않고 UI를 즉시 업데이트 하는 방법이다.

  • 사용자 경험 개선
  • 애플리케이션이 더 빠르고 반응성이 좋아보이게 만듬

일반적인 흐름

  1. 사용자가 데이터를 변경하는 작업 수행
  2. UI에서 변경된 결과를 즉시 반영
  3. 서버에 변경 요청을 전송
  4. 서버 응답이 성공인 경우, UI 유지
  5. 서버 응답이 실패인 경우, 이전 상태로 롤백하거나 에러처리

적용

기존 목표 불러오기 코드

export const useSidebarGoalsMutation = () => {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: postSidebarGoals,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.SIDEBAR_GOALS] });
      queryClient.invalidateQueries({
        queryKey: [QUERY_KEYS.TODOS_OF_GOALS],
      });
    },
    onError: (error) => {
      console.error(error.message);
      notify('error', '목표 등록에 실패했습니다.', 3000);
    },
  });
};

기존 코드에 낙관적 업데이트를 적용하기 위해서
onMutate을 작성하고 onSuccess 대신 성공, 실패 여부에 관계 없이 실행하기 위해 onSettled를 작성
그에 맞는 onError 코드도 변경하였다.

낙관적 업데이트 적용한 코드


export const useSidebarGoalsMutation = () => {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (postData: PostGoalTypes) =>
      POST<GoalsResponse, PostGoalTypes>(API_ENDPOINTS.GOAL.GOALS, postData),
    onMutate: async ({ title }) => {
      const prev = queryClient.getQueriesData({ queryKey: [QUERY_KEYS.GOALS] });

      await queryClient.cancelQueries({ queryKey: [QUERY_KEYS.GOALS] });
      queryClient.setQueryData([QUERY_KEYS.GOALS], (oldData: GoalsResponse) => {
        if (!oldData?.data) return oldData;

        return {
          ...oldData,
          data: [
            ...oldData.data,
            {
              goalId: Date.now(),
              goalTitle: title,
              color: '#848484',
              createAt: new Date().toISOString(),
            },
          ],
        };
      });

      return { prev };
    },
    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.GOALS] });
      queryClient.invalidateQueries({
        queryKey: [QUERY_KEYS.TODOS_OF_GOALS],
      });
    },
    onError: (error, newGoal, context) => {
      queryClient.setQueriesData(
        { queryKey: [QUERY_KEYS.GOALS] },
        context?.prev,
      );
      console.error(error.message);
      notify('error', `목표 등록에 실패했습니다.\n ${newGoal.title}`, 3000);
    },
  });
};

onMutate

서버에 요청을 보내기 전에 실행한다.

해당 쿼리의 기존 데이터를 prev 로 저장
cancelQueries로 목표 관련 쿼리를 중단해 기존 데이터와 서버 응답의 충돌을 방지
기존 값 (oldData) 에 새로운 데이터를 추가해 UI를 즉시 갱신
prev를 리턴해준다.

onSettled

서버 요청이 성공하거나 실패한 후에 실행한다.

onSettled로 관련된 쿼리 키들을 invalidate하여 최신 데이터를 가져오도록 함

onError

서버의 응답이 실패하면 실행한다.

onMutate에서 저장한 prev를 사용해 쿼리를 복구한다.

  • context는 onMutate에서 반환된 값이다.

에러 메시지를 통해 사용자에게 알린다.

결과

PR 주소: https://github.com/slid-todo/front/pull/116

profile
안녕하세요. 프론트엔드 개발 공부를 하고 있습니다.

0개의 댓글

관련 채용 정보