[React-Query] 리액트 쿼리 사용하기(useQuery, useMutation)

yiwoojung·2022년 10월 28일
13

React

목록 보기
10/12
post-thumbnail

React-Query는 비동기 로직을 리액트스럽게 다룰 수 있게 해주는 라이브러리이다. 이를 사용하면 server state를 아주 효율적으로 관리할 수 있다.

React-Query 설치 및 기본 세팅

설치

npm i react-query
yarn add 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가 실행되는 것이 아닌 두개의 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

React-Query docs를 참고하면 더 많은 옵션들을 볼 수 있다.

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: TDdata) => 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분
    });
  • fresh 상태인 1분 동안은 아무리 다른 탭을 왔다 갔다해도 fetch 요청을 하지 않는다.

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 addSuperHero = (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. 같은 앱에 리액트가 여러개 있거나
일 때 이런 에러가 난다고 한다.

나는 리액트 공식 홈페이지를 통해 에러를 해결했다.

여태까지 React-Query를 사용하기 전까지는 useEffect 안에서 필요한 api를 실행하도록 코드를 작성했어서 useQuery할 때도 useEffect 안에서 불러왔는데 이게 문제였다.

아래로 더 내려가서 읽어보면 다음과 같은 글이 있는데 마지막 문구를 보면 useEffect 안에서 Hooks를 호출하지 말라고 한다.

useEffect, useMemo useReducer 안에서 React-Query를 사용하면 안된다!

Apollo-Client를 사용할 때도 useEffect를 쓰지는 않고 useQuery를 바로 불러왔기 때문에 Apollo-Client와 유사하게 사용하면 될 것 같다.



Reference

profile
프론트엔드 개발자

0개의 댓글