[React] 낙관적 업데이트(optimistic update)

정다롱·2025년 1월 6일

🚩 낙관적 업데이트란?

낙관적 업데이트

  • 서버 통신 전에 상태를 먼저 업데이트 한 뒤 통신을 진행하는 방식이다.
  • ex) SNS 등의 좋아요, 북마크 버튼 등
    - 사용자의 상호작용 직후 UI에 반영되어 사용자 경험을 높일 수 있다
  1. 사용자의 상호작용
  2. 서버 요청 전 클라이언트 상태(ui) 업데이트
  3. 백그라운드에서 서버 통신 진행
  4. 통신 성공 => 현재 상태 유지
  5. 통신 실패 => 이전 상태로 롤백

React에서는 redux, zustand, context-api 등등 상태 관리 라이브러리를 사용하여 여러 방법으로 낙관적 업데이트를 구현할 수 있다.

나는 여러 프로젝트에서 서버 상태를 관리할 때 tanstack query를 사용했기 때문에 tanstack query의 onMutate, onError를 통해 낙관적 업데이트를 구현할 수 있었다.

tanstack query의 경우 서버 통신 후 데이터를 관리할 때 onError, onLoading, mutation, success 등의 여러 옵션을 통해 상태 관리와 오류 흐름을 간편하게 관리할 수 있어서 자주 사용했는데, 낙관적 업데이트 또한 빠르게 구현할 수 있어서 좋았다.

tanstak query 공식 문서


❤️ 좋아요 낙관적 업데이트 (코드)

실제로 구현한 코드는 아래와 같다.

// 좋아요 낙관적 업데이트
export const useToggleLikeButton = (
  user: User | null,
  postId: number,
  isLike: boolean,
) => {
  const queryClient = useQueryClient();

  const { mutate } = useMutation({
    // 서버에 좋아요 상태를 요청하는 함수
    mutationFn: () => toggleLike(user, isLike, postId),

    // 실제로 낙관적 업데이트를 수행하는 부분 (onMutate)
    onMutate: async () => {
      // query 요청 충돌을 방지하기 위해 기존 요청을 취소한다.
      await queryClient.cancelQueries({ queryKey: ["like", user?.id] });

      // prev 라는 이름으로 이전 상태가 될 현재 상태를 저장한다.
      const previousUserLikes = queryClient.getQueryData<Tables<"post">[]>([
        "like",
        user?.id,
      ]);

      // 서버 요청이 완료된 것처럼 UI 상태를 미리 업데이트한다.
      if (user?.id) {
        queryClient.setQueryData<Tables<"post">[]>(
          ["like", user.id],
          (old = []) => {
            if (isLike) {
              return old.filter((post) => post.post_id !== postId);
            } else {
              return [...old];
            }
          },
        );
      }

      return { previousUserLikes };
    },

      // 서버 요청에서 오류가 발생하면 아까 저장해둔 이전 상태로 다시 변경한다.
    onError: (err, variables, context) => {
      // 에러 시 유저의 좋아요 목록만 롤백
      if (user?.id) {
        queryClient.setQueryData(["like", user.id], context?.previousUserLikes);
      }
    },

      // 요청이 완료되면 쿼리 무효화를 통해 최신 서버 상태와 클라이언트 캐시를 동기화한다.
    onSettled: () => {
      // 모든 관련 쿼리 무효화
      queryClient.invalidateQueries({ queryKey: ["like", user?.id] });
      // 좋아요 개수 업데이트 해주기
      queryClient.invalidateQueries({ queryKey: ["like", postId] });
    },
  });

  return mutate;
};

주석으로 낙관적 업데이트의 흐름을 다시 파악해보았다.
처음 낙관적 업데이트를 구현할 때 가장 처음에 cancelQueries가 왜 필요한지 몰랐는데 검색해보니까 캐시가 덮어쓰이거나 하는 등의 충돌을 방지하기 위함이라고 한다. 그래서 오류 방지로 고분고분하게 쓰는 중...

말로만 들었을 땐 어려워 보여서 내가 이런걸 할 수 있다고예..? 했는데 막상 해보니까 코드만 길지 크게 어렵지 않아서 여기저기 쓰이면 좋을 것 같은 부분에 자주 쓰고 있다. 좋아요 버튼 눌렀는데 한 1초 2초 지나서 하트가 반짝이는 것 보다 즉시 눈에 보이는 게 사용자에게 더 좋을테니까~

0개의 댓글