본문 바로가기
SPA/React

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

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

안녕하세요. J4J입니다.

 

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

 

 

 

이전 글

 

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

 

 

반응형

 

 

getQueryData Filter 사용 변경

 

react-query에서 query 처리를 수행할 때 활용될 수 있는 공통 기능 중 첫 번째 변경된 사항은 getQueryData입니다.

 

getQueryData는 query key에 매핑되어 있는 캐싱된 데이터가 존재하는 경우 해당 데이터를 불러와 사용할 수 있도록 도와줍니다.

 

그래서 getQueryData를 사용할 땐 query key의 값을 넣어줘야 했고 이외에 추가적으로 query filter라는 것을 활용할 수 있었습니다.

 

 

 

query filter에서 활용해 볼 수 있는 값들은 react-query 공식 문서를 확인해 보시면 알겠지만 key에 매핑되는 데이터가 stale 한 상태인지, 현재 status 값은 무엇인지 등이 존재합니다.

 

filter를 설정했다면 캐싱된 데이터가 존재하더라도 filter에 적용한 것까지 같이 고려되어 모두 해당되는 경우에만 데이터가 불러와지도록 도와줍니다.

 

 

 

하지만 5버전이 등장하면서부터 filter의 사용이 삭제되었습니다.

 

개인적으로도 filter를 사용해 본 적은 없었기도 했고 공식 문서도에서도 왜 filter를 삭제했는지에 대해 언급한 것이 없기에 명확한 삭제 이유는 알지 못하는 상황입니다.

 

 

 

코드로 살펴보면 5버전 이전에는 다음과 같이 getQueryData를 사용할 때 두 번째 파라미터에 query filter의 값을 넣어줄 수 있었습니다.

 

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

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

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

            <div>
                {/* use query key and add filter */}
                <h2>queryData</h2>
                {queryClient
                    .getQueryData<string[]>(
                        ['strings'],
                        // set query filter
                        {
                            type: 'active',
                            stale: true,
                        },
                    )
                    ?.map((string) => <p key={string}>{string}</p>)}
            </div>
        </main>
    );
}

 

 

 

하지만 5버전 이후부터 filter가 삭제되었기에 다음과 같이 query key만 파라미터로 넣을 수 있도록 변경되었습니다.

 

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

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

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

            <div>
                {/* only use query key */}
                <h2>queryData</h2>
                {queryClient.getQueryData<string[]>(['strings'])?.map((string) => <p key={string}>{string}</p>)}
            </div>
        </main>
    );
}

 

 

 

 

getQueryState Filter 사용 변경

 

getQueryData와 마찬가지로 getQueryState에서도 query filter의 사용이 삭제되었습니다.

 

getQueryState는 단순하게 캐싱된 데이터만 가져오던 getQueryData와 달리 query key에 매핑된 query의 현재 상태 값까지 더 확인할 수 있는 함수입니다.

 

캐싱된 데이터 뿐만 아니라 언제 데이터가 업데이트되었고, 현재 status는 무엇인지 등을 확인할 수 있는 함수인데 이 또한 query filter의 사용이 삭제되어 사용 방법은 getQueryData와 동일하게 변경되었습니다.

 

 

 

그래서 5버전 이전에는 다음과 같이 사용될 수 있었습니다.

 

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

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

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

            <div>
                {/* use query key and add filter */}
                <h2>queryState</h2>
                {queryClient
                    .getQueryState<string[]>(
                        ['strings'],
                        // set query filter
                        {
                            type: 'active',
                            stale: true,
                        },
                    )
                    ?.data?.map((string) => <p key={string}>{string}</p>)}
            </div>
        </main>
    );
}

 

 

 

하지만 5버전 이후에는 getQueryData와 동일하게 query key만 사용할 수 있도록 변경되었습니다.

 

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

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

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

            <div>
                {/* only use query key */}
                <h2>queryState</h2>
                {queryClient.getQueryState<string[]>(['strings'])?.data?.map((string) => <p key={string}>{string}</p>)}
            </div>
        </main>
    );
}

 

 

 

 

keepPreviousData 설정 방법 변경

 

keepPreviousData는 query key 값이 변경되어 새로운 데이터를 조회할 때 조회된 데이터가 확인될 때까지 이전의 데이터가 계속 보이도록 도와주는 설정입니다.

 

예를 들어 적용 전후는 다음과 같습니다.

 

keepPreviousData 적용 전

 

keepPreviousData 적용 후

 

 

 

위와 같은 역할을 수행하는 keepPreviousData의 설정 방법이 5버전 이후 변경되었습니다.

 

기존에는 query의 options에 keepPreviouData를 다음과 같이 설정하는 방식이었습니다.

 

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

export default function KeepPreviousData() {
    const [page, setPage] = useState<number>(1);
    const res = useQuery<string[]>({
        queryKey: ['strings', page],
        queryFn: async () =>
            (
                await axios.get('http://localhost:8080/strings/pages', {
                    params: {
                        page,
                        limit: 2,
                    },
                })
            ).data,
        // set keepPreviousData config in keepPreviousData
        keepPreviousData: true,
    });

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

            <div>
                <h2>Page</h2>
                <button onClick={() => setPage(page + 1)}>increase</button>
                <button onClick={() => setPage(page - 1)}>decrease</button>
            </div>
        </main>
    );
}

 

 

 

하지만 5버전 이후부터는 keePreviousData가 options에서 삭제되었고 기존에 존재하던 placeholderData에 keepPreviousData를 설정하는 것으로 변경되었습니다.

 

그래서 위와 동일한 코드를 다음과 같이 변경 작성해 볼 수 있습니다.

 

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

export default function KeepPreviousData() {
    const [page, setPage] = useState<number>(1);
    const res = useQuery<string[]>({
        queryKey: ['strings', page],
        queryFn: async () =>
            (
                await axios.get('http://localhost:8080/strings/pages', {
                    params: {
                        page,
                        limit: 2,
                    },
                })
            ).data,
        // set keepPreviousData config in placeholderData
        placeholderData: keepPreviousData,
    });

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

            <div>
                <h2>Page</h2>
                <button onClick={() => setPage(page + 1)}>increase</button>
                <button onClick={() => setPage(page - 1)}>decrease</button>
            </div>
        </main>
    );
}

 

 

 

 

Suspense Hook 추가

 

5버전 이후부터는 suspense 기능을 적용할 수 있는 새로운 hook이 추가되었습니다.

 

먼저 suspense에 대해 소개를 드리면 suspense는 API 요청을 했을 때 응답이 오기 전까지 fallback UI를 제공해 줄 수 있도록 도와줍니다

 

react에서는 기본적으로 suspense를 위한 기능을 제공해 주는 것이 있고 해당 기능을 활용하여 react-query에서는 suspense 설정만 하면 쉽게 적용해 볼 수 있었습니다.

 

그래서 5버전 이전에는 다음과 같이 suspense 기능을 활용해 볼 수 있습니다.

 

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

function Suspense() {
    // if need to suspense, set suspense option
    const res = useQuery<string[]>({
        queryKey: ['strings'],
        queryFn: async () => (await axios.get('http://localhost:8080/strings')).data,
        // suspense set true
        suspense: true,
    });

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

export default function ReactQuerySuspense() {
    return (
        <React.Suspense fallback={<p>Suspense...</p>}>
            <Suspense />
        </React.Suspense>
    );
}

 

 

 

5버전 이후부터는 useSuspense로 시작하는 suspense를 위한 hook이 등장했습니다.

 

하지만 hook이 등장하면서 suspense를 사용하기 위해 options에 설정하던 suspense 속성 값이 삭제되었습니다.

 

그래서 suspense 기능을 활용하고 싶다면 이제부터는 useSuspense hook을 활용하여 다음과 같이 적용해 볼 수 있습니다.

 

// prev react-query-v5
import { useSuspenseQuery } from '@tanstack/react-query';
import axios from 'axios';
import React from 'react';

function Suspense() {
    // if need to suspense, use SuspenseQuery
    const res = useSuspenseQuery<string[]>({
        queryKey: ['strings'],
        queryFn: async () => (await axios.get('http://localhost:8080/strings')).data,
        // removed suspense option
    });

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

export default function ReactQuerySuspense() {
    return (
        <React.Suspense fallback={<p>Suspense...</p>}>
            <Suspense />
        </React.Suspense>
    );
}

 

 

 

 

isDataEqual 사용 변경

 

5버전 이전에는 isDataEqual을 활용하여 구조적인 관점에서 최적화를 시킬 수 있었습니다.

 

isDataEqual의 역할은 캐싱되어 있는 데이터와 API를 통해 조회된 데이터를 정의한 대로 서로 비교한 뒤 데이터에 차이가 없다고 판단되면 기존 캐싱되어 있는 데이터를 동일하게 유지되도록 도와줍니다.

 

비록 캐싱되어 있는 데이터와 새롭게 조회된 데이터가 동일한 값이 아니더라도 함수를 어떻게 정의하냐에 따라 동일한 값으로도 판단할 수 있기에 범용적으로 활용해 볼 수 있습니다.

 

isDataEqual은 다음과 같이 사용할 수 있었습니다.

 

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

export default function IsDataEqual() {
    const res = useQuery<string[]>({
        queryKey: ['strings'],
        queryFn: async () => (await axios.get('http://localhost:8080/strings')).data,
        // if length is same, use old data
        isDataEqual: (oldData, newData) => {
            return oldData?.length === newData.length;
        },
    });

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

 

 

 

5버전 이후부터는 isDataEqual이 삭제되고 5버전 이전에도 존재하던 option인 structuralSharing을 활용하여 동일하게 적용하게 변경됩니다.

 

그래서 위와 동일한 코드를 다음과 같이 작성해볼 수 있습니다.

 

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

export default function IsDataEqual() {
    const res = useQuery<string[]>({
        queryKey: ['strings'],
        queryFn: async () => (await axios.get('http://localhost:8080/strings')).data,
        // if length is same, use old data
        structuralSharing: (oldData, newData) => {
            return oldData?.length === newData.length ? oldData : newData;
        },
    });

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

 

 

 

 

Query Remove 삭제

 

query remove는 말 그대로 query와 관련된 것들을 모두 삭제하고 query를 호출하기 이전으로 돌리는 기능을 의미합니다.

 

대표적으로 remove와 비교해볼 수 있는 것은 invalidate가 있습니다.

 

둘의 차이점에 대해 간단히 얘기를 해보면 invalidate는 query key에 담겨있는 캐싱된 데이터를 삭제시키는 기능으로, invalidate를 수행하게 되면 렌더링 되어 있는 컴포넌트들 중 query key에 해당되는 query는 다시 api를 호출하게 됩니다.

 

하지만 remove는 렌더링되어 있는 컴포넌트가 있더라도 다시 api를 호출하지 않고, 리렌더링을 하게 되면 query를 처음 호출하는 것처럼 isLoading 단계부터 시작하여 다시 api를 호출하게 됩니다.

 

 

 

이런 remove를 수행할 수 있는 방법으로 5버전 이전에는 다음과 같이 query의 result를 활용하여 사용해 볼 수 있었습니다.

 

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

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

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

            <div>
                {/* if want to remove query, use result */}
                <button onClick={() => res.remove()}>query remove</button>
            </div>
        </main>
    );
}

 

 

 

하지만 5버전 이후부터는 remove 함수가 삭제되었습니다.

 

그리고 queryClient를 활용하여 query를 삭제하는 방식을 이용해야 하며 해당 방식은 5버전 이전부터도 존재했기 때문에 결과적으로 remove 하는 방법을 하나로 통일했다고 생각할 수 있습니다.

 

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

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

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

            <div>
                {/* if want to remove query, use queryClient */}
                <button
                    onClick={() =>
                        queryClient.removeQueries({
                            queryKey: ['strings'],
                        })
                    }
                >
                    query remove
                </button>
            </div>
        </main>
    );
}

 

 

 

 

refetchInterval 파라미터 변경

 

refetchInterval은 해당 option에 설정한 시간 값을 주기로 삼아 refetch를 수행하도록 도와줍니다.

 

사용하는 방식에 있어서 크게 변경된 것은 없지만 파라미터를 받아오는 과정에 변경점이 존재합니다.

 

5버전 이전에는 파라미터를 활용하면 다음과 같이 캐싱된 데이터와 query 데이터를 사용할 수 있었습니다.

 

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

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

        // use both data and query
        refetchInterval: (data, query) => {
            console.log(data);
            console.log(query.state.data);
            return false;
        },
    });

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

 

 

 

하지만 5버전 이후부터는 캐싱된 데이터를 파라미터로 받아오지 않게 변경되었습니다.

 

캐싱된 데이터를 파라미터로 받아오지는 않지만 query 데이터를 활용하여 캐싱된 데이터를 확인할 수 있기 때문에 이 또한 데이터를 확인하는 방법을 하나로 통일했다고 생각할 수 있습니다.

 

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

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

        // data removed, query can also check data
        refetchInterval: (query) => {
            console.log(query.state.data);
            return false;
        },
    });

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

 

 

 

 

onSuccess, onError, onSettled 삭제

 

개인적으로 query들보다는 mutation에서 많이 사용하던 onSuccess, onError, onSettled이 더 이상 사용하지 못하게 삭제되었습니다.

 

사실 변경점을 확인하기 전에 있는지도 몰랐던 option들인데 삭제되는 것을 보니 몰라도 되었던 option들이었구나 하는 생각이 들기는 했습니다.

 

어찌 되었든 5버전 이전에는 다음과 같이 사용할 수 있었습니다.

 

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

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

        // use onSuccess, onError, onSettled
        onSuccess: () => {
            console.log('onSuccess!');
        },
        onError: () => {
            console.log('onError!');
        },
        onSettled: () => {
            console.log('onSettled!');
        },
    });

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

 

 

 

하지만 5버전 이후부터는 위와 같이 사용한다면 에러가 발생되기에 더 이상 활용할 수 없게 되었습니다.

 

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

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

        // if use onSuccess, onError, onSettled, occur error !
        // onSuccess: () => {
        //     console.log('onSuccess!');
        // },
        // onError: () => {
        //     console.log('onError!');
        // },
        // onSettled: () => {
        //     console.log('onSettled!');
        // },
    });

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

 

 

 

 

 

 

이상으로 react-query v5 변경점 알아보기 두 번째인 query 공통 기능 변경에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

 

 

 

728x90
반응형

댓글