Tanstack-Query@V5(이거 완전 RTK Query잖아?)

신태일·2024년 10월 17일
post-thumbnail

💡 본인은 리덕스를 상당히 애용하는 편으로 캐시된 데이터를 조작하려고 할때 자연스럽게 RTK Query를 도입하고 쏠쏠하게 써먹었던 경험이 있었습니다.
하지만 점점 전역 상태 로직에 대해서 이해를 어느정도 하고 나니 리덕스는 정석 그 자체이지만 이것보다 조금 더 가벼운 Zustand, Recoil에 대해 학습을 어느정도 하고 나니까 굳이 리덕스를 작은 어플리케이션 구성 시 사용할 이유가 있을까? 라는 생각이 들더군요…
따라서 이번 기회에 많은 사람들이 애용하는 Tanstack-Query에 대해서도 학습을 하고 기존 코드를 그에 맞게 리팩토링 과정도 거칠려고 합니다.

우선 늘 참고사항의 우선이 되야 하는 공식 문서부터 보고 가시길 바랍니다 !

Overview | TanStack Query Docs


React-Query 설치 및 기본 세팅


설치

npm i @tanstack/react-query

or

yarn add @tanstack/react-query

기본 세팅

import { QueryClient, QueryClientProvider } from "react-query";

const queryClient = new QueryClient();

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Home />
    </QueryClientProvider>
  );
}
  • 모든 페이지에서 리액트 쿼리를 사용하기 위해서 애플리케이션 최상단(App.js)에서 QueryClientProvider로 앱을 감싸준다.
  • 쿼리 인스턴스를 생성한다. const queryClient = new QueryClient();
  • 생성한 인스턴스를 넘겨준다. client={queryClient}

01. useQuery

  • GET 요청과 같은 Read 작업을 할 때 사용되는 Hook이다.
  • 첫번째 파라미터로 queryKey가 들어가고 두번째 파라미터로 비동기 함수(api호출 함수)가 들어간다.
  • return값은 api의 성공여부, 실패여부, api return 값을 포함한 객체이다.
  • useQuery는 비동기로 작동한다. 즉, 한 컴포넌트에 여러 개의 useQuery가 있다면 동기적으로 실행되는 것이 아닌 두개의 useQuery가 동시에 실행된다. 그러므로 여러개의 비동기 query가 있다면 useQuery보다는 useQueries를 사용하는 것이 좋다.
  • 혹은 Query Options에서 enabled를 사용하면 useQuery를 동기적으로 사용할 수 있다.
    import { useQuery } from "react-query";
    
    // 주로 사용되는 3가지 return값 외에도 더 많은 return 값들이 있다.
    const { data, isLoading, error } = useQuery(queryKey, queryFn, options);

1-1. Query Key

  • ❗️QueryKey를 기반으로 데이터 캐싱을 관리한다.
  • 쿼리키는 문자열 또는 배열로 저장할 수 있다.
  • 쿼리키를 배열로 지정하면 배열의 0번 값은 string으로 다른 컴포넌트에서 부를 키를 넣어주면 되고 1번째 값은 query함수의 파라미터로 전달될 값을 넣어주면 된다.
    // 문자열
    useQuery('todos', ...)
    // 배열
    useQuery(['todos', '1'], ...)
  • ❗️쿼리가 변수에 의존하는 경우에는 Query Key에도 해당 변수를 추가해주어야 한다.
    const { data, isLoading, error } = useQuery([’todos’, id], () ⇒ axios.get(`http://…/${id}`));
  • 첫번째 파라미터로 설정한 QueryKey는 다른 컴포넌트에서도 해당 키를 사용하면 호출 가능하다.

1.2 Query Functions

  • useQuery의 두 번째 인자에는 promise를 반환하는 함수를 넣어주어야 한다.
  • 다음의 방식들로 작성할 수 있다.
    - useQuery('todos', fetchTodos);
    - useQuery(['todos', todoId], () => fetchTodoById(todoId));
    
    - useQuery(['todos', todoId], async () => {
    		const data = await fetchTodoById(todoId);
    		return data
    	});
    - useQuery(['todos', todoId], ({ queryKey }) => fetchTodoById(queryKey[1]));

1-3 Query Options

  • 공식문서를 참고하면 더 많은 옵션들을 볼 수 있어요 !
    • enabled(boolean)
      • 쿼리가 자동으로 실행되지 않게 설정하는 옵션이다.

        // id가 존재할 때만 쿼리 요청을 한다.
        const { data } = useQuery(
        	['todos', id],
        	() => fetchTodoById(id),
        	{
        		enabled: !!id,
        	}
        );
    • retry(boolean | number | (failureCount: number, error: TError) ⇒ boolean)
      • default: 3회
      • 실패한 쿼리를 재시도하는 옵션이다.
      • true 로 설정하면 쿼리 실패시 무한 재시도하고 false로 설정하면 재시도를 하지 않는다.
    • staleTime(number | Infinity)
      • default: 0
      • 해당 시간이 지나면 stale 상태가 된다.
      • 데이터가 fresh 상태로 유지되는 (stale 상태가 되기까지의) 시간이다.
        • fresh 상태에서는 다시 mount되어도 fetch가 실행되지 않는다.
    • cacheTime (number | Infinity)
      • default: 5분
      • inactive 상태인 캐시 데이터가 메모리에 남아있는 시간이다. 이 시간이 지나면 캐시 데이터는 가비지 컬렉터에 의해 메모리에서 제거된다.
    • refetchOnMount (boolean | “always”)
      • default: true
      • 데이터가 stale 상태일 경우 마운트 시 마다 refetch를 실행하는 옵션이다.
      • always로 설정하면 마운트 시 마다 매번 refetch를 실행한다.
    • refetchOnWindowFocus (boolean | “always”)
      • default: true
      • 데이터가 stale 상태일 경우 윈도우 포커싱 될 때 마다 refetch를 실행하는 옵션이다.
      • always로 설정하면 항상 윈도우 포커싱 될 때 마다 refetch를 실행한다는 의미이다.
    • refetchOnReconnect (boolean | “always”)
      • default: true
      • 데이터가 stale 상태일 경우 재 연결될 때 refetch를 실행하는 옵션이다.
      • always로 설정하면 위에 두 옵션처럼 쿼리가 매번 재 연결될 때마다 refetch를 실행한다.
    • onSuccess ((data: TData) ⇒ void)
      • 쿼리 성공 시 실행되는 함수이다.
      • 매개변수 data는 성공 시 서버에서 넘어오는 response 값이다.
    • onError ((error: TError) ⇒ void)
      • 쿼리 실패 시 실행되는 함수이다.
      • 매개변수로 에러 값을 받을 수 있다.
    • onSettled ((data?: TData, error?: TError) ⇒ void)
      • 쿼리가 성공하면 성공한 데이터가 전달되거나, 실패하면 에러가 전달될 때 실행되는 함수이다.
      • 매개변수로 성공 시엔 성공 데이터, 실패 시에는 에러가 전달된다.
    • initialData (TData | () ⇒ TData)
      • initialData 를 설정하면 쿼리 캐시의 초기 데이터로 사용된다. (쿼리가 아직 생성되지 않았거나 캐시되지 않았을 때)
      • staleTime 이 설정되지 않은 경우 초기 데이터는 기본적으로 stale 상태로 간주한다.

❗️useQuery 예시

const { data: tasks, isLoading } = useQuery("getTasks", getTodo, {
		refetchOnWindowFocus: false,
		staleTime: 60 * 1000, // 1분
	});
const { data: tasks, isLoading } = useQuery("getTasks", getTodo, {
		refetchOnWindowFocus: false,
		staleTime: 60 * 1000, // 1분
		onError: (error) => {
			if (error.response?.data.code === 401) {
				// ...
			}
		}
	});

02. useMutation

  • 데이터 변경 및 삭제 하는 메서드
  • POST, PUT, DELETE와 같은 변경 및 수정 작업을 할 때 사용되는 훅이다.
const data = useMutation(API 호출 함수, 콜백);
  • return 값은 useQuery와 동일하며 mutate 메서드가 추가된다.
  • mutate 메서드를 이용하면 API 요청 함수를 호출하여 요청이 이루어진다.

❗️ 쿼리 무효화 (Invalidation)

Add 후 수동적으로 Fetch를 해줘야 화면에 보여진다는 불편함이 있다.

이 문제점을 해결하기 위해서는 쿼리 무효화(Invalidation)를 시켜줘야 한다.

이 전에 캐싱된 쿼리를 직접 무효화 시킨 후 데이터를 새로 패칭해줄 수 있다.

import { useMutation, useQueryClient } from 'react-query';

const AddSuperHero = () => {const queryClient = useQueryClient();

	const addSuperHeror = (hero) => {
		return axios.post('http://localhost:4000/superheroes', hero);
	};

	const { mutate: addHero, isLoading, isError, error } = useMutation(addSuperHero, {
		onSuccess: () => {
		// 캐시가 있는 모든 쿼리 무효화
		✅ queryClient.invalidateQueries();

		// queryKey가 'super-heroes'로 시작하는 모든 쿼리 무효화
		✅ queryClient.invalidateQueries('super-heroes');
		}
	});

	const handleAddHeroClick = () => {
		const hero = { 이름, 성별 };
		addHero(hero);
	};

	if (isLoading) {
		return <h2>Loading...</h2>;
	}

	if (isError) {
		return <h2>{error.message}</h2>;
	}
}

Error

1. Invalid Hook Call Warning

useQuery를 사용할 때 다음과 같은 에러가 날 수 있다.

react-dom.development.js:16227 
Uncaught Error: Invalid hook call. 
Hooks can only be called inside of the body of a function component. 
This could happen for one of the following reasons: 
1. You might have mismatching versions of React and the renderer (such as React DOM) 
2. You might be breaking the Rules of Hooks 
3. You might have more than one copy of React in the same app
  1. React와 React DOM의 버전이 맞지 않거나
  2. Hooks 규칙을 지키지 않고 있거나
  3. 같은 앱에 리액트가 여러개 있거나

위 사항에 하나라도 해당하면 이런 에러가 난다고 한다.

보통 fetch, axios, … 같은 패칭 훅은 useEffect, useMemo, useReducer 등등 에서 쓰이곤 했는데 React-Query는 React-Hook 내부에서 쓰면 안되고 독립적으로 쓰여야 한다! (이것마저도 RTK-Query와 똑같아서 사용하는데 오히려 반가울 것 같다 ㅎㅎ)

profile
노원거인

0개의 댓글