ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [TanStack Query] Optimistic Update(낙관적 업데이트)로 사용자 경험 개선하기
    React 2024. 3. 11. 19:29

    최근에 프론트엔드 사용자 경험 개선에 관해 공부를 하다가 

    낙관적 업데이트라는 개념을 알게 되었습니다.

     

    이왕 공부해 본 김에 프로젝트에 한번 적용해 보면 좋을 거 같아서 

    TanStack Query를 사용하여 적용해보고자 합니다.

    | Optimistic Update(낙관적 업데이트)

    낙관적 업데이트는 프론트엔드 개발에서 사용되는 데이터 동기화 전략입니다.

    사용자가 데이터를 수정하거나 추가할 때 서버로부터 오는 응답을 기다리지 않고,

    UI를 즉시 업데이트 하는 방식입니다.

     

    낙관적 업데이는 사용자가 서버의 응답을 기다리지 않고 지연 없이 즉시 피드백받게 되므로,

    사용자 경험 개선에 큰 도움이 될 수 있습니다.

     

    다만 오류가 발생했을 때 이를 적절히 처리하고 원상 복구 시키는 로직을 잘 작성해야 하고,

    금전적인 정보, 사용자 개인 정보 등 민감한 정보에는 사용에 주의하여야 합니다.

    | 사용

    프로젝트에서 어떤 아이템에 싫어요를 클릭하거나 해제할 때

    낙관적 업데이트가 적용되도록 해보겠습니다.

     

    먼저 아이템에 싫어요 요청을 하는 요청 함수를 만들어 보겠습니다.

    import axios from "axios";
    
    const doDislike = async (id: number) => {
      const res = await axios.post(`API주소`);
      return res.data;
    };
    
    export default doDislike;

     

    리액트 쿼리로 해당 함수를 호출할 수 있도록 mutation을 반환하는 훅을 하나 만들어 보겠습니다.

    또한 성공하면 해당되는 아이템의 쿼리를 무효화해 줍니다.

    import { useMutation, useQueryClient } from "@tanstack/react-query";
    import doDislike from "../../../api/markers/doDislike";
    
    const useDoDislike = (id: number) => {
      const queryClient = useQueryClient();
      
      return useMutation({
        mutationFn: doDislike,
        onSuccess() {
          queryClient.invalidateQueries({ queryKey: ["item", id] });
        },
      });
      
    };
    
    export default useDoDislike;

     

    이런 방식으로 하면 아래 이미지와 같이 싫어요를 클릭하면
    서버에서 응답이 올 때까지 기다린 후 UI를 업데이트해 줍니다.

     

    그럼 이제 낙관적 업데이트를 적용해 보겠습니다.

    작동 방식은 다음과 같습니다.

    1. 싫어요 요청
    2. 요청 중에 미리 ui 업데이트
    3. 만약 요청 실패시 이전으로 복구

     

    여기서 리액트 쿼리 useMutation의 onMutate를 사용하면

    요청 중에 실행될 로직을 작성할 수 있습니다.

    따라서 기존 싫어요 요청 훅 코드를 다음과 같이 수정해 줍니다.

    import { useMutation, useQueryClient } from "@tanstack/react-query";
    import doDislike from "../../../api/markers/doDislike";
    import type { Marker } from "../../../types/Marker.types";
    
    const doDislike = (id: number) => {
      const queryClient = useQueryClient();
      
      return useMutation({
        mutationFn: doDislike,
        
        onMutate: async () => {
          await queryClient.cancelQueries({ queryKey: ["item", id] });
          
          // 먼저 해당 아이템의 업데이트 이전 정보를 저장합니다.
          const previousMarkerData: Marker = queryClient.getQueryData([
            "item",
            id,
          ]) as Marker;
    	
          if (previousMarkerData.dislikeCount) {
          	// 만약 해당 아이템 데이터에 dislikeCount가 있으면
    	    // 숫자를 1 더해주고 disliked를 true로 해줍니다.
            queryClient.setQueryData(["item", id], {
              ...previousMarkerData,
              disliked: true,
              dislikeCount: previousMarkerData.dislikeCount + 1,
            });
          } else {
            // 아니면 dislikeCount를 1로 넣어주고 disliked를 true로 해줍니다.
            queryClient.setQueryData(["item", id], {
              ...previousMarkerData,
              disliked: true,
              dislikeCount: 1,
            });
          }
    	  
          // 실패 시 되돌릴 수 있도록 이전 값을 리턴해 줍니다.
          return { previousMarkerData };
        },
      });
      
      // 실패 시 이전 데이터로 복구
      onError(_error, _hero, context?: { previousMarkerData: Marker }) {
        if (context?.previousMarkerData) {
          queryClient.setQueryData(["item", id], context.previousMarkerData);
        }
      },
      
      // 실패 성공 여부에 상관없이 해당 쿼리를 무효화 해줍니다.
      onSettled() {
        queryClient.invalidateQueries({ queryKey: ["item", id] });
      },
    };
    
    export default doDislike;

     

    이런 식으로 코드를 작성하면 낙관적 업데이트가 적용되어서

    아래 이미지와 같이 즉각적인 피드백을 얻을 수 있습니다.

Designed by Tistory.