React-Query 도입기

elin·2021년 7월 2일
32

React

목록 보기
2/2
post-thumbnail

현재 개발하고 있는 서비스는 state관리를 위해 Redux + Redux-saga + Redux-Toolkit(RTK) 을 사용하고 있었다. 이 조합을 사용하는데에 있어서 익숙해 지다보니 크게 불편함은 없었지만 최근에 몇가지 단점이 제기되었다.

📌 실제 서버 데이터 외에 Loading Flag, Error Flag 가 많다. 🧐

  • 서버로부터 데이터를 가져오는중 (GET/POST/DELETE등 요청을 했는데 아직 response가 도착하기 전일 경우) 이라는 것을 UI에 Loader로 보여줘야 하기 때문에 필요했다.

  • Error 가 났으면 노티를 준다던지 page를 이동한다던지 하는 로직을 구현하기 위해 필요했다.

📌 서버와의 통신 + 데이터 가공을 위해 작성하는 똑같은 모양의 코드가 너무 많다. 🤭

  • "API 호출을 했는지 (즉, Action이 발생했는지) watch하고 있다가 -> API 호출 -> response 기다리는 중.. -> 완료!" 이런 반복되는 동작들을 처리하고 데이터를 가공하는데에 쓰이는 똑같은 모양의 코드가 너무 많았다.

이러한 단점으로 인해 Redux+Redux-saga를 대체할 새로운 라이브러리로 떠오른게 React-Query였다.
React-Query가 잘하는 것은 서버 상태를 캐싱하는 것이다.
이 외에도 Devtool을 제공, API call -> 간단한 후처리 코드 작성, Options으로 Polling,Pagination 지원 등 다양한 장점이 있다.

이 포스트에서는 React-Query 를 도입하면서 겪었던 이슈들을 정리해보려고 한다.

React-Query

React-Query 공식 문서

React-Query란 공식 문서에 의하면 'Fetch, cache and update data in your React and React Native applications all without touching any "global state".' 이다.
즉, 'React Application에서 서버 상태를 가져오고, 캐시하고, 동기화하고, 업데이트하는 작업을 수월하게 해주는 라이브러리' 이다.

React-Query를 사용해 서버 상태를 가져오기 위해서는 useQuery를 사용하면 된다.
만약 서버의 data를 수정하고자 한다면 useMutation 을 사용한다.
요약하면 R ( Read ) 은 useQuery , CUD ( Create, Update, Delete )는 useMutation을 사용하면 된다.

자세한 사용법은 React-Query 공식 문서의 가이드 문서를 통해 확인할 수 있다. ( React-Query 공식 가이드 문서 )

이 포스트는 React-Query 사용법을 소개하는 포스트는 아니고 실제 적용하면서 겪었던 것을 기록하고 React-Query를 도입하고자 하는 또 다른 누군가에게 참고가 되었으면 해서 적게 되었다.

React-Query를 도입하면서 겪었던 이슈들

1️⃣ refetch 가 된 후 loading 상태는 false 이다.

useQuery의 return 값으로 API의 fetch 상태를 얻어올 수 있다.

  const { isLoading, error, data, isFetching } = useQuery("repoData", () =>
    fetch(
      "https://api.github.com/repos/tannerlinsley/react-query"
    ).then((res) => res.json())
  );

fetch를 아직 완료하지 않은 상태일 때, UI에 Loader를 그려주고자 isLoading을 사용할 수 있을 것이다.
❗️하지만 주의해야할 점이 있다❗️
isLoading === true의 조건은 '캐시된 data가 없고 isFetching === true 일 때' 이다.
즉, refetch 가 된 query 일 경우 isLoading 이 false 라는 얘기이다.
만약 의도적으로 refetch 시에도 Loader를 그려주고 싶다면 isLoading이 아닌 isFetching을 사용해야 한다.

2️⃣ useMutation 사용시 서버의 response를 받고 싶을 땐?

useQuery를 사용하면 server의 reponse를 useQuery의 return 값인 data에서 받아올 수 있다.

  const fetchTodoList = () => {
    const { data } = await axios.get(
      "https://..."
    );
    return data;
  }
  
  const { data } = useQuery("todos", fetchTodoList);

하지만 useMutation의 경우, CUD에 해당하는 API 콜 후, 서버의 response를 data에서 받아올 수 없다. ( 성공/실패 여부는 당연히 알 수 있다! )

  • const { data: todoList } = useQuery('todos', fetchTodoList); ⭕️
    • todoList는 fetchTodoList의 결과값
  • const { data: todo } = useMutation(postTodo);
    • API 콜이 성공하더라도 undefined 혹은 unknown

React-Query 공식 문서에서도 useMutation의 return 값인 data의 타입은 'undefined | unknown' 으로 명시되어 있다. 만약 useMutation 사용시 response 가 필요한 경우라면 mutateAsync 로 얻어올 수 있다.
mutateAsync는 Promise를 return 하게 되고 Promise result로 response를 가져올 수 있다.

const postTodo = (todo) => { 
  axios.post('/api/data', { todo }) 
}
const createTodo = useMutation(postTodo); 

createTodo.mutateAsync(todo).then((data) => {
   console.log(data);
   // console로 찍은 data가 서버의 response 값입니다.
});

3️⃣ useIsMutating vs queryClient.isMutating

React-Query의 QueryClient 는 캐시를 이용해 여러 기능을 사용할 수 있게 해준다.
그 중 하나인 queryClient.isMutating method는 현재 mutating 중 (진행 중..) 인 mutation의 수를 반환한다.

import { QueryClient } from 'react-query'
 
 const queryClient = new QueryClient();
 const isMutating = queryClient.isMutating();

useIsMutating 이라는 hook 역시 현재 mutating 중인 mutation의 수를 반환한다.

 import { useIsFetching } from 'react-query'
 // How many queries are fetching?
 const isFetching = useIsFetching()

얼핏 보면 두 가지 모두 같은 기능을 하는 것 처럼 보인다. 하지만 차이점이 있기 때문에 적절하게 사용하는 것이 필요하다.

🟠 useIsMutating 같은 경우에는 component에서 값의 업데이트를 알 수 있다. *

  • useIsMutating 내부적으로 MutationCache를 subscribe 하고 있기 때문에 mutating 중인 mutation 수에 변화가 있으면 component 에서 감지할 수가 있는 것이다. 당연하게도 subscribe 하고 있던 component ( useMutating을 사용하던 component )가 unmount 되는 순간 unsubscribe 를 한다

예를 들어보겠다.

TodoList 를 관리하는 페이지가 있다고 해보자.
이 TodoList에 새로운 Todo를 추가하기 위해서는 'Todo 추가하기'라는 모달을 통해 추가하면 된다.
( 임의로 만든 매우 이상한 모달이지만 어쨌든 이 모달로 예를 들겠다.😅)

추가 할 일 입력 후 '추가하기' 버튼을 누르고 POST 요청이 성공하면 모달이 unmount 되고 TodoList에서는 방금 추가한 할 일이 추가되어 있어야 한다.

createTodo.mutate(todo, { 
  onSuccess: () => closeModal(); // 모달을 unmount 하는 함수
  onSettled: () => queryClient.refetchQueries('todos') // 완료 후 todoList refetch
});

TodoList의 코드 내부에는 useIsMutating 을 사용해 POST 요청이 mutating 중인지 확인하고 Loader를 돌리는 로직이 존재한다고 가정한다. 그런데 위와 같이 로직을 짜면 무한으로 Loader가 보이는 현상이 발생할 수 있다.

❓ 그 이유는?

  • useMutation을 통해 POST 요청을 하던 'Todo 추가하기' 모달이 onSettled가 완료되기 이전에 unmount 되었기 때문이다. 모달이 unmount 된 순간 unsubscribe를 할 것이고 isMutating 값이 업데이트되지 않을 수 있다.

이런 실수를 방지하기 위해서는 onSuccess/onError/onSettled 내부에 unmount 하는 로직은 넣지 않는 것이 좋을 것 같다.

반면 queryClient.isMutating을 사용하면 바뀐 mutating값을 조회할 수 있다. 하지만 useIsMutating과 다르게 업데이트 감지는 안되기 때문에 주의해야 한다.

4️⃣ redux를 사용해 관리했던 서버 상태가 아닌 global state는?

React-Query는 서버 상태를 관리하는데에 유용한 라이브러리이다 . 하지만 서버 상태가 아닌 global state 를 관리하기 위해서는 다른 라이브러리를 함께 사용하는 것도 좋을 것 같다.
나 역시 global state 를 관리할 수 있는 다른 라이브러리를 둘러보다가 constate를 발견하게 되었다. 실제로 도입하지는 않았지만 공부했던 내용에 대해서 다음 포스트에 정리해보도록 하겠다.

profile
아자아자!

1개의 댓글

comment-user-thumbnail
2022년 1월 16일

글 잘읽었습니다!
궁금한점이 있는데요!

try catch로 CUD 작업하는거랑

useMutation으로 작업하는거랑 어떤 차이가 있는건가요 ??

답글 달기