낙관적 업데이트
- 서버 통신 전에 상태를 먼저 업데이트 한 뒤 통신을 진행하는 방식이다.
- ex) SNS 등의 좋아요, 북마크 버튼 등
- 사용자의 상호작용 직후 UI에 반영되어 사용자 경험을 높일 수 있다
React에서는 redux, zustand, context-api 등등 상태 관리 라이브러리를 사용하여 여러 방법으로 낙관적 업데이트를 구현할 수 있다.
나는 여러 프로젝트에서 서버 상태를 관리할 때 tanstack query를 사용했기 때문에 tanstack query의 onMutate, onError를 통해 낙관적 업데이트를 구현할 수 있었다.
tanstack query의 경우 서버 통신 후 데이터를 관리할 때 onError, onLoading, mutation, success 등의 여러 옵션을 통해 상태 관리와 오류 흐름을 간편하게 관리할 수 있어서 자주 사용했는데, 낙관적 업데이트 또한 빠르게 구현할 수 있어서 좋았다.
실제로 구현한 코드는 아래와 같다.
// 좋아요 낙관적 업데이트
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초 지나서 하트가 반짝이는 것 보다 즉시 눈에 보이는 게 사용자에게 더 좋을테니까~