본문 바로가기
프로그래밍/React

# React-Js Master Class #5-3 Crypto Tracker

by 퐁당당당 2025. 2. 17.

5.10,11 React Query

React Query를 활용하면 api에서 정보를 fetch해오는 과정을 조금 더 간단하게 만들 수 있다.

마지막 5단원 정리에서는 react query를 활용해서 데이터를 받아오는 과정은 기존 과정과 어떤 차이를 가지고 있고, 차이점에 따른 장점들에 대해 정리해보려고 한다.

0) intro

(1) Previous Method

우선 기존에 api에서 데이터를 받아오는 코드를 살펴보자.

//Coin.tsx
useEffect(()=>{
  (async()=>{
    const response = await fetch("https://api.coinpaprika.com/v1/coins");
    const json = await response.json();
    setCoins(json.slice(0,100));
    setLoading(false);
  })();
},[])

async함수와 fetch를 사용하여 데이터를 받아오고 있다.

setLoading이라는 setter함수를 사용하고 있는 것을 보니, loading중임을 표현하는 함수도 useEffect밖에서 하나 선언해준 것을 확인해볼 수 있다.

(2) Problems

이렇게 필요한 파일 내에서 api를 가져오는 것의 문제는

  1. fetching의 과정을 재사용할 수 없다.
  2. loading하는 과정을 직접 관리해야 한다 (useState훅을 이용하여..)
  3. data를 받을 변수도 별개로 관리해야 한다 (마찬가지로 useState활용)

결국 한 마디로 정의하면, fetching의 재사용이 불가능하다는 점과, api를 가져오기 위한 정보들이 각각 분산되어 있다는 점에서 이 방식은 조금 불편하다.(data, loading etc..)

2) useQuery

(1) Setting+Installing

npm i @tanstack/react-query

react 18 이상은 react-query랑 충돌을 일으키기 때문에 @tanstack 이라고 적고 시작해야 한다.

const queryClient=new QueryClient();

function App() {
  return (
  <QueryClientProvider client={queryClient}>
    <ThemeProvider theme={theme}>
      <Router/><GlobalStyle/>
    </ThemeProvider>
  </QueryClientProvider>);
}

설치 이후, App전체를 query client로 감싸주어야 한다.

(2) api.ts

이후, 아까 있던 코드를 useQuery를 활용하여 만들어보자.

일단 fetcher함수를 api.ts로 따로 빼서 작성해볼 것이다.

//api.ts

export async function fetchCoins(){
  const response = await fetch("https://api.coinpaprika.com/v1/coins");
  const json = await response.json();
  return json;
}

가독성을 위해 Promise를 사용하여 적어보자

export async function fetchCoins(){
  return fetch("https://api.coinpaprika.com/v1/coins").then((response)=>
  response.json());
}

지금부터는 아까 useEffect가 있었던 파일에서 fetcher함수와 useQuery를 활용하여 data를 받는 과정을 살펴보려고 한다.

(3) Implementation

  • useQuery로 객체 생성

우선 useQuery훅을 사용하여 객체를 만들어보자.

useQuery("allCoins", fetchCoins);
  1. “allCoins”: query key → data를 한 줄 요약해둔 정보. 이 데이터의 이름같은 요소다.
  2. fetchCoins : 아까 api.ts에 만들어둔 fetcher function을 가져온다.
  • return값 구조분해할당
const {isLoading, data}=useQuery<ICoins[]>("allCoins", fetchCoins);
  1. isLoading : 로딩중에 대한 정보를 담고 있는 isLoading이다. 이 값은 처음에 true를 가지고 있다 data를 다 받아오면 이 값을 false로 바꾼다.
  2. data : api에서 받아온 정보를 담고 있는 변수다.
  3. <ICoins[]> : data에 대한 정보(type interface)가 담겨있다. Ts에게 data의 type에 대해 설명하는 것이다.

이런식으로 useQuery훅이 생성한 객체 안에 loading에 대한 정보와, 받아온 데이터에 대한 정보가 모두 포함되어 있어 세트로 관리할 수 있다.

const { isLoading, data } = useQuery<ICoins[]>(["allCoins"], fetchCoins);

return <Container>
  <Header>
    <Title> 코인 </Title>
  </Header>
  {isLoading?(
    <Loader>"Loading..."</Loader>):
    (<CoinList>
    {data?.map(coin=>
    <Coin key={coin.id}>
      <Link to={{
        pathname:`/${coin.id}`,
        state:{name:coin.name}
      }}>
  1. data? : data값이 있다면 뒤를 실행해라

받아온 값들은 이렇게 사용할 수 있다.

useQuery를 쓰는 것의 장점은 우선

  1. loading의 과정과 fetching의 과정 분리 가능
  2. fetcherFunction 외부에 분리하여 코드의 가독성이 올라감
  3. useQuery로 간단하게 처리 가능
  4. data caching

이라고 볼 수 있다.

data를 caching하는 과정은 언급 안했는데, Coins 페이지에서 → coin 페이지로 간 이후 뒤로가기를 누르면 loading을 하지 않고 바로 보여지는 걸 확인할 수 있다. 이처럼 useQuery를 사용하면 데이터 캐싱이 알아서 진행된다.

3) useQuery with Props

지금부터는 Coin페이지에 가서 api를 fetch해오는 과정들을 모두 useQuery를 사용하도록 바꿔주려고 한다.

그런데 문제는 Coins페이지에서 Coin하위 페이지로 넘어갈 때 Coin Id값을 보내야 하는데, api.ts로 fetcher함수를 빼주면, 접근이 어려워질 것 같다는 문제점을 안고 있다.

(1) api.ts

그렇다면 Coins페이지에서 useQuery를 부를 때 prop을 사용해주면 될 것이다.

interface ICoinId{
  coinId:string;
}

export async function fetchInfo({coinId}: ICoinId){
  return fetch(`https://api.coinpaprika.com/v1/coins/${coinId}`).then((response)=>response.json());
}

export async function fetchPriceInfo({coinId}:ICoinId){
  return fetch(`https://api.coinpaprika.com/v1/tickers/${coinId}`).then((response)=>response.json());
}

이런식으로 coinId를 prop으로 받아온 후, url에 추가하여 fetch를 진행할 수 있다.

(2) call

const {isLoading, data}=useQuery<ICoins[]>(["allCoins"], fetchCoins);

const {isLoading, data}=useQuery(["info", coinId], ()=>fetchInfo(coinId))
  1. ["info", coinId] : 원래는 받아올 데이터의 이름만 적어주면 되는데, 여기서는 coinId까지 추가했다. props를 받아오는 경우에는 변화하는 변수도 적어주어야 한다.
  2. ()=>fetchInfo(coinId) : 원래는 저 자리에 함수 이름만 적었다. 그러나 prop을 포함한 함수를 적어주고 싶다면, 새로운 함수를 만들어서 그 return값으로 prop값을 포함한 함수를 적어주면 된다.

그냥 fetchInfo(coinId)라고 적으면, 실행과 동시에 함수가 돌아가기 때문에 저렇게 새로운 함수를 만들어서 return값에 할당을 해주는 것이다.

(3) avoiding repetition

만약에 useQuery를 한 번만 사용한다면 문제될 게 없지만, 두 번 이상 사용한다면 isLoading이나, datat라는 변수의 이름이 중복되기 때문에 문제가 생긴다.

해당 프로퍼티를 구조분해할당 하지만, 이름을 바꾸려면 어떻게 해야 하는지 확인해보자

const {isLoading:infoLoading, data:infoData}=useQuery(["info", coinId], ()=>fetchInfo(coinId))
const {isLoading:priceLoading, data:priceData}=useQuery(["price", coinId], ()=>fetchPriceInfo(coinId))
  1. isLoading:infoLoading :

    1. isLoading

      일단 객체에서 isLoading프로퍼티를 가지고 와야 하니 저 변수를 적어준다.

    2. infoLoading

      내가 사용하고 싶은 이름 (=infoLoading)을 적어준다.

(4) Tips

겹치는 Url들은 저렇게 Base_url로 묶어서 적는 것이 훨씬 효율적이다.

const BASE_URL=`https://api.coinpaprika.com/v1`

export async function fetchCoins(){
  return fetch(`${BASE_URL}/coins`).then((response)=>
  response.json());
}

3)Sum

지금까지 useQuery를 활용하여 loading에 대한 정보와, data에 대한 정보를 한 세트로 받아 관리하는 방법에 대해 배웠다.

복습할 때는 useQuery를 배우기 전에 제기했던 문제점들과, 각 변수들의 의미, 추가적으로 prop이 포함된 fetcher함수를 작성하는 방법을 집중적으로 공략하면 좋을 듯 하다.