본문 바로가기
SPA/React

[React] react-query v5 변경점 알아보기 (5) - useMutation 기능 변경

by J4J 2023. 12. 13.
300x250
반응형

안녕하세요. J4J입니다.

 

이번 포스팅은 react-query v5 변경점 알아보기 마지막인 useMutation 기능 변경에 대해 적어보는 시간을 가져보려고 합니다.

 

 

 

이전 글

 

[React] react-query v5 변경점 알아보기 (1) - 공통 기능 변경

[React] react-query v5 변경점 알아보기 (2) - query 공통 기능 변경

[React] react-query v5 변경점 알아보기 (3) - useQueries 기능 변경

[React] react-query v5 변경점 알아보기 (4) - useInfiniteQuery 기능 변경

 

 

반응형

 

 

Optimistic Updates 방식 추가

 

useMutation의 변경점에 대해서는 한 가지에 대해서만 적어보도록 하겠습니다.

 

먼저 여기서 말하고자 하는 optimistic updates라는 것은 UX 친화적인 최적의 업데이트 처리를 수행하는 것을 의미합니다.

 

예를 들어 다양한 SNS에서 제공해주고 있는 "좋아요 기능"에 대해 말씀드리면 사용자가 특정 게시물에 대해 좋아요 기능이 담겨있는 아이콘을 클릭하게 되면 대부분 다음과 같은 데이터 처리 흐름을 가지게 됩니다.

 

  • 좋아요 아이콘 클릭
  • 클릭 이벤트 발생에 의한 사용자의 좋아요 처리 API 호출
  • 좋아요 처리된 게시글의 현재 좋아요 상태 조회 API 호출
  • 조회된 API 기반으로 좋아요 클릭 여부 아이콘 UI 제공

 

 

 

이런 과정을 거치면서 결과적으로 사용자가 게시글을 좋아요 눌렀다는 아이콘이 보이는 순간은 가장 마지막 순서가 될 것입니다.

 

하지만 저런 과정들을 거치면서 만약 중간 처리 과정에서 처리가 지연된다면 사용자 입장에서는 분명 좋아요를 눌렀지만 좋아요를 눌렀다는 아이콘이 UI에 보이지 않기 때문에 UX 측면에서 좋지 못한 결과를 만들 수 있습니다.

 

그래서 이런 이슈들을 제거하고자 optimistic updates를 활용한 다양한 방식을 통해 UX들을 개선하는 모습들을 많이 볼 수 있습니다.

 

 

 

 

5버전으로 넘어오면서부터 뿐만 아니라 그 이전부터 react-query에서는 optimistic updates 방식에 대한 가이드를 지속적으로 제공해주고 있었습니다.

 

react-query 공식 문서를 확인해보면 현재는 총 2가지의 방법을 확인할 수 있고 이전부터 있던 방법은 "캐시를 활용한 방법" 이고 새롭게 등장한 방법은 "UI를 활용한 방법" 입니다.

 

참고적으로 이 2가지 방법들은 5버전에서도, 5버전 이전에서도 모두 활용이 가능합니다.

 

 

 

5버전 이전부터 가이드되고 있던 캐시를 활용한 방법에 대해서 먼저 보겠습니다.

 

위에서 optimistic updates가 필요한 경우에 대해 간단히 적어놨는데 이번엔 optimistic updates를 적용할 수 있는 방법에 대해서 간단히 순서를 작성해 보겠습니다.

 

  • 좋아요 아이콘 클릭
  • 클릭 이벤트 발생에 의한 사용자의 좋아요 처리 API 호출
  • 현재 좋아요 상태 snapshot 처리
  • API 처리와 관련 없이 브라우저 환경에서 좋아요 아이콘 클릭에 대한 변경점 즉시 적용하여 클릭 여부 아이콘 UI 제공
  • 좋아요 처리된 게시글의 현재 좋아요 상태 조회 API 호출
  • 브라우저 환경에서 임의 처리한 것을 상태 조회된 API의 결과물로 교체
  • 이 모든 과정에서 문제가 발생했을 경우 snapshot 처리된 값을 활용하여 기존 값으로 롤백

 

 

 

이런 과정들을 활용한다면 사용자 입장에서는 API 처리와 관련 없이 즉각적으로 사용자가 행동한 이벤트 처리가 화면에 바로 보이는 것을 볼 수 있습니다.

 

그리고 react-query에서는 이런 optimistic updates에 대한 것을 다음과 같이 처리할 수 있도록 가이드하고 있습니다.

 

참고적으로 모든 API 처리는 3초의 딜레이를 가지게 설정했습니다.

 

// prev react-query-v5
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import axios from 'axios';

export default function Variables() {
    const queryClient = useQueryClient();
    const res = useQuery<string[]>({
        queryKey: ['strings'],
        queryFn: async () => (await axios.get('http://localhost:8080/strings')).data,
    });

    const mutation = useMutation({
        mutationFn: async (string: string) =>
            await axios.post('http://localhost:8080/strings', {
                string,
            }),
        // mutate가 호출된 경우
        onMutate: async (newString: string) => {
            // 어떠한 외부 refetch가 발생되어도 query가 변경되지 않고 유지
            await queryClient.cancelQueries({ queryKey: ['strings'] });
            // 이전 캐싱 데이터를 snapshot 처리
            const prevStrings = queryClient.getQueryData(['strings']);
            // query key에 새로운 데이터 임의로 추가하기
            queryClient.setQueryData<string[]>(['strings'], (prevStrings) =>
                prevStrings ? [...prevStrings, newString] : [newString],
            );
            // context로 반환
            return {
                prevStrings,
            };
        },
        // error가 발생된 경우
        onError: (_error, _newString, context) => {
            queryClient.setQueryData(['strings'], context?.prevStrings);
        },
        // error or success가 발생된 경우 refetch 수행
        onSettled: () => {
            queryClient.invalidateQueries({
                queryKey: ['strings'],
            });
        },
    });

    return (
        <main>
            <div>
                <h2>Strings</h2>
                {res.data?.map((string) => <p key={string}>{string}</p>)}
            </div>

            <div>
                <button onClick={() => mutation.mutate('newString')}>add newString</button>
            </div>
        </main>
    );
}

 

error가 발생된 경우

 

error가 발생하지 않은 경우

 

 

 

결과물을 확인해시면 우선 API 처리와 관련 없이 새로운 문자가 즉시 사용자 관점에서 추가되는 것을 볼 수 있습니다.

 

이후로는 API 처리 과정에서 에러가 발생된다면 snapshot으로 남겨 둔 이전 데이터의 상태로 롤백하는 것이 확인됩니다.

 

하지만 에러가 발생하지 않으면 새롭게 조회된 데이터로 자연스럽게 교체되어 UX를 높이는 결과를 만들 수 있습니다.

 

 

 

 

이번엔 5버전 이후 react-query에서 새롭게 가이드하고 있는 UI를 활용한 방법에 대해 보겠습니다.

 

캐싱을 활용할 땐 html보단 JS 중심의 코드 작성이 이루어졌지만 UI를 활용할 땐 JS보단 html 중심의 코드 작성이 이루어집니다.

 

위와 동일하게 동작되는 코드를 UI 관점으로 변경하면 다음과 같이 작성할 수 있습니다.

 

// react-query-v5
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import axios from 'axios';

export default function Variables() {
    const queryClient = useQueryClient();
    const res = useQuery<string[]>({
        queryKey: ['strings'],
        queryFn: async () => (await axios.get('http://localhost:8080/strings')).data,
    });

    const mutation = useMutation({
        mutationFn: async (string: string) =>
            await axios.post('http://localhost:8080/strings', {
                string,
            }),
        // error or success가 발생된 경우 refetch 수행
        // promise 형태의 return을 수행해야 refetch가 모두 종료될때까지 'pending' 상태를 유지
        onSettled: async () => {
            return await queryClient.invalidateQueries({
                queryKey: ['strings'],
            });
        },
    });

    return (
        <main>
            <div>
                <h2>Strings</h2>
                {res.data?.map((string) => <p key={string}>{string}</p>)}
                {/* pending 되는 동안 refetch가 발생된 것 처럼 ui를 제공 */}
                {mutation.isPending && <p>{mutation.variables}</p>}
                {/* error가 발생되면 retry를 위한 ui를 제공 */}
                {mutation.isError && (
                    <>
                        <p>{mutation.variables} add failed...</p>
                        <button onClick={() => mutation.mutate(mutation.variables)}>retry</button>
                    </>
                )}
            </div>

            <div>
                <button onClick={() => mutation.mutate('newString')}>add newString</button>
            </div>
        </main>
    );
}

 

error가 발생한 경우

 

error가 발생하지 않은 경우

 

 

 

위의 결과물처럼 캐시를 활용할 때와 동일하게 optimistic updates 처리가 잘 수행되는 것을 볼 수 있습니다.

 

개인적인 생각으로 이 두 가지 관점을 모두 확인해 봤을 때 무엇이 더 좋다고 말할 순 없을 것 같습니다.

 

결국 개발자의 취향과 개발되는 상황에 따라 사용 방식이 구분될 수 있다고 느껴지기에 두 가지 방식에 대해 모두 완벽히 이해를 한 뒤 더 선호되는 개발 방향으로 적용해 보시는 것이 가장 올바를 것 같습니다.

 

 

 

 

 

 

 

 

 

이상으로 react-query v5 변경점 알아보기 마지막인 useMutation 기능 변경에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

 

 

 

728x90
반응형

댓글