요즘 공고를 보다보면 react-query 사용자가 우대한다는 글이 많다.
도대체 리액트쿼리가 뭐기에 이렇게 많이들 요구하는 걸까?
그래서 공부해본다!

  • 영어를 한국어로 번역한거라 오류가 있을 수 있으니 댓글로 피드백 주시면 감사히 수정하겠습니다! *

What is React Query?

리액트 애플리케이션에서 데이터를 가져오기 위한(fetching data) 라이브러리다.

  1. 리액트가 UI 라이브러리로, 데이터를 가져오기 위한 특정한 패턴이 없었다.
  2. data fetching을 위해 useEffect과 loading, error, data 결과값과 같이 컴포넌트의 상태를 유지하기 위햇 useState를 사용했다.
  3. 앱 전체에 데이터가 필요한 경우, 보통 상태 관리 라이브러리를 사용한다.
  4. 대부분 상태관리 라이브러리들은 클라이언트쪽 데이터를 관리하는데 적합하다.
  5. 그러나, 서버 쪽 데이터나 비동기에 적합하지 않은 점들이 있다.
Client state
클라이언트가 자체적으로 생성한 상태로 사용자의 UI 업데이트가 동기화되기 때문에
서버에서 일어나는 일과는 관계가 없는 상태를 의미한다.

Server state
클라이언트에 표시하는 데 필요한 서버 데이터를 가져오거나 업데이트하기 위해 비동기 API가 필요하다.
그러나 캐시나 동일한 데이터에 대한 여러가지 중복된 요청을 제거하는것, 그리고 백그라운드에서 오래된 데이터들을 업데이트하고, 성능을 최적화할때 어려움을 겪을 수 있다.

따라서 React Query는 리액트 애플리케이션에서 서버의 응답에 대한 상태인 fetch, caching, update와 같은 동작을 쉽게 다룰 수 있도록 한다.
그리고 Client state과 Server state를 분리해서 관리할 수 있도록 해주는 라이브러리다.


React-query를 사용한 프로젝트 setup

1. CRA를 사용해서 새로운 리액트 프로젝트를 만든다.

npx create-react-app [프로젝트 이름]

2. 애플리케이션에서 사용하게 될 mock data를 제공하는 API의 endpoint를 설정한다.

  • https://www.npmjs.com/package/json-server
    에서 JSON Server를 설치해준다.

     터미널에서 npm install json-server
  • 이후 mock data를 만들기 위해 db.json을 만든다.

    그리고 안에 데이터를 만들어준다.

{
  "products": [
    {
      "id": 1,
      "name": "dress",
      "price": "219000",
    },
    {
      "id": 2,
      "name": "shoes",
      "price": "159000",
    },
    {
      "id": 3,
      "name": "pants",
      "price": "109000",
    }
  ]
}
  • package.json파일에서 "scripts"에 다음과 같이 추가해준다.

    "server-json": "json-server --watch db.json --port 4000"

port 번호는 React port 번호와 다르게 설정하는데 보통 4000번이나 3001번으로 설정한다.

  • 터미널에서 아래와같이 입력한 뒤

    json-server --watch ./db.json --port 4000;

  • 주소창에 http://localhost:4000/products를 입력하면 앞에서 입력한 mock 데이터들을 확인할 수 있다.

3. 애플리케이션에서 react-router와 몇가지 라우터들을 설정한다.

  • 새로운 터미널을 열어서 react-router-dom을 설치해준다.

    npm i react-router-dom

그리고 App.js에서 라우터를 설정한다.
(라우터에 관해서는 포스팅하지 않겠음.)

4. useEffectuseState를 사용하여 기존 방식으로 데이터를 가져온다.

  • fetch 또는 axios를 사용하면 된다.
    주로 나는 axios를 사용하기 때문에 axios를 설치해준다.

    npm i axios

  • 그리고 이제 이 products들을 불러올 컴포넌트에서 useEffectuseState를 사용해서 데이터를 가져온다.
// fetch data using react-query

import axios from "axios";
import { useState, useEffect } from "react";

const Products = () => {
  const [isLoading, setIsLoading] = useState(true);
  const [data, setData] = useState([]);

  useEffect(() => {
    axios.get("http://localhost:4000/products").then((res) => {
      console.log(res);
      setData(res.data);
      setIsLoading(false);
    });
  }, []);

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

  return (
    <>
      <h1>Products Page</h1>
      {data.map((product) => {
        return <div key={product.name}>{product.name}</div>;
      })}
    </>
  );
};

export default Products;
  • 만약! 여기서 axios에러가 발생한다면 그건 분명 server가 꺼져있기 때문이다.
    (본인 이야기 아님.)
    이럴 경우 위에서 입력했던것처럼 다시 새로운 터미널을 열어서 아래와 같이 입력해준다.

    json-server --watch ./db.json --port 4000;

그리고 다시 새로고침해보면 axios에러가 사라지고 해당 products의 이름이 출력되는걸 볼 수 있다.

react-query 설치

  • 다시 새로운 터미널을 열어서 아래와 같이 react-query를 설치해준다.

    npm install @tanstack/react-query

react-query-devtools 설치

  • react-query를 추가했으면, react-query 전용 devtools도 설치해준다.
    react-query-devtools는 리액트 쿼리 개발자들이 쉽게 사용할 수 있도록 도와주는 툴로, 쿼리가 어떻게 작동되는지 보여주는 것이다.
    터미널에 아래와 같이 입력해서 설치해준다.

    npm i @tanstack/react-query-devtools

설치가 끝났다면 이제 index.js에서 관련 속성을 적용해준다. Redux를 사용해봤다면 조금 비슷할 것 같다.

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

const queryClient = new QueryClient();

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <BrowserRouter>
    <QueryClientProvider client={queryClient}>
      <App />
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  </BrowserRouter>
);

React Query Devtools을 설정하면 화면 아래에 이쁜 꽃 모양이 나온다.
클릭해보면 fetch한 API에 대한 정보를 받아올 수 있다.

React-query 적용하기

useQuery

  • get API요청을 수행하기 위한 훅
  • post, update는 useQuery가 아니라 useMutations를 사용해야한다.
  • 비동기로 여러 useQuery를 사용하려면 useQueries를 사용한다.

useQuery는 기존의 방법과 조금 달라졌다.

기존 useQuery 사용 방법

const fetchData = async () => {
    const res = await axios.get('');
    
    return res.data;
  }

const queryfetch = useQuery('query-key', fetchtempData, {
    onSuccess: (data)=> {
      console.log(data)
    },
  })

이전에는 query-key가 문자열로 선언하면 되지만, 현재는 배열안에 query key 값을 넣어줘야한다.

현재 useQuery사용 방법

const queryfetch = useQuery(['query-key'], fetchtempData, {
    onSuccess: (data)=> {
      console.log(data)
    },
  })

이를 통해 useQuery를 사용해서 위에서 설정한 products를 불러오는 코드를 아래와 같이 작성해줬다.

// fetch data using react-query

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

const Products = () => {
  const fetchData = async () => {
    const res = await axios.get("http://localhost:4000/products");

    return res.data;
  };

  const { status, data, error } = useQuery(["products"], fetchData);

  if (status === "loading") {
    return <h1>Loading...</h1>;
  }

  if (status === "error") {
    return <h1>Error: {error.message}</h1>;
  }

  return (
    <>
      <h1>React Query Products</h1>
      {data &&
        data.map((product) => {
          return <div key={product.name}>{product.name}</div>;
        })}
    </>
  );
};

export default Products;

반환값으로 status, data, error를 설정했는데 이는 다음과 같다.

  • status: 쿼리의 상태로 isLoading, isSuccess와 같은 상태를 status로 처리할 수 있다. loading, error, success 3가지 종류가 있다.
  • data: fetch한 데이터로, 데이터가 fetch될 때까지 undefined 값을 가지게 된다.
  • error: 오류가 발생한 경우를 말하며, 오류가 없을 시 undefined의 값을 가지게 된다.
  • isFetching: 쿼리가 fetching 중인 여부를 boolean 값으로 나타낸다.

번외) TypeError: Cannot read property 'map' of undefined 에러

return문을 보면 &&로 되어있지만 처음에는 data?.data로 코드를 만들었다.

 {data?.data.map((product) => {
    return <div key={product.name}>{product.name}</div>;
 })}

그렇게해서 실행시켰더니 TypeError: Cannot read property 'map' of undefined 발생.

생긴 이유:

React는 렌더링이 커밋된 후 효과를 실행한다. 만약 data?.data.map으로 반복 실행하게 된다면, 첫 번째턴은 undefined로 아직 데이터가 들어와있지 않은 상태에서 렌더링이 실행되기 때문에 오류가 발생한다.

해결 방법 초기값 설정

첫번째 렌더링이 될 때 데이터가 들어와있지 않기 때문에 값이undefined가 되면서 발생하는 에러이기 때문에 초기값을 설정해주면 된다.

const [data, setData] = useState([]); 

위의 방법처럼 data의 초기값을 [] 비어있는 배열로 설정해주면 해결할 수 있다.

해결 방법 &&

따라서 &&로 변경시켜주면 된다.

      {data &&
        data.map((product) => {
          return <div key={product.name}>{product.name}</div>;
        })}

&&는 true일 경우에만 그 뒤의 코드가 실행되고, 만약 false일 경우에는 뒤의 요소가 출력되지 않기 때문에 무시하고 건너뛴다.
따라서 오류가 발생하지 않게 된다.

profile
👩🏻‍💻

0개의 댓글