Crypto Tracker 4.10 _ ReactQuery Part Two

ReactQuery Part Two

useQeury 훅으로 fetcher()를 이용해 state와 fetch 함수를 대신한다는 것을 알 수 있었다.
인자로 isLoading 과 data를 넣었는데
전자는 fetcher가 끝났는지 확인하는 것이고, data는 json파일을 저장하는 것이다.

화면을 바꿨다 돌아와도, ReactQuery는 우리가 원하는 데이터가 이미 cache(캐시) 되어 있다는 것을 알고 있기에 API에 접근하지 않는다. 이게 아주 큰 차이이다.
그래서 "Loading..." 이라는 화면이 보이지 않는 것이다.

+) 팁
react query는 tools가 있는데

Devtools 는 render 할 수 있는 component 이고 이것이 뭐냐,
React Query에 있는 dev tools를 import 해오면 나의 캐시에 있는 query를 볼 수 있다.
그래서 App.js에서 Devtools를 rener할 것이다.



그럼 이런 창으로 확인 가능하다

query를 시각화하여 볼 수 있는 방법이다.

이제 Coin 코드를 정리해보자 !!

api.ts 파일에

function 2개를 만든다.


그리고 coin.tsx에서 fetch함수를 사용하는데, 이때 같은 키를 사용하는 것은 안되므로
고유한 키를 사용하도록 한다.



우리는 그래서 array로 만든 다음,

고유한 id를 갖도록 한다.


이렇게 배열을 넣어주고, 이제 {} 안에 isLoading을 넣어 줄 수 있다, 하지만 이때

같은 이름을 적은 것을 바꿔준다,

코드를 변경한다


이제 두가지 query를 확인할 수 있다...
많은곳애 들어갈 수록 캐시에 쌓여 Loading도 점점 더 안하게 된다.

Coin.tsx

import { useEffect, useState } from 'react';
import { useQuery } from 'react-query';
import { Switch, Route, useLocation, useParams, useRouteMatch } from 'react-router';
import { Link } from 'react-router-dom';

import styled from 'styled-components';
import Chart from './Chart';
import Price from './Price';
import { fetchCoinInfo, fetchCoinTickers } from './api';
//useParams 는 URL에서 관심있어 하는 정보를 잡아낼 수 있게 해준다.

function Coin() {
  const { coinId } = useParams<RouteParams>();
  const { state } = useLocation<RouteState>();
  const priceMatch = useRouteMatch('/:coinId/price');
  const chartMatch = useRouteMatch('/:coinId/chart');

  const { isLoading: infoLoading, data: infoData } = useQuery<InfoData>(['info', coinId], () =>
    fetchCoinInfo(coinId)
  );
  const { isLoading: tickersLoading, data: tickerData } = useQuery<PriceData>(
    ['tickers', coinId],
    () => fetchCoinTickers(coinId)
  );
  //같은 키를 쓰면 좋지 않으므로 key가....여기서 멈춤

  // const [loading, setLoading] = useState(true);
  // const [info, setInfo] = useState<InfoData>();
  // const [priceInfo, setPriceInfo] = useState<PriceData>();

  // useEffect(() => {
  //   (async () => {
  //     const infoData = await (await fetch(`https://api.coinpaprika.com/v1/coins/${coinId}`)).json();
  //     const priceData = await (
  //       await fetch(`https://api.coinpaprika.com/v1/tickers/${coinId}`)
  //     ).json();
  //     setInfo(infoData);
  //     setPriceInfo(priceData);
  //     setLoading(false);
  //   })();
  // }, [coinId]);

  const loading = infoLoading || tickersLoading;
  return (
    <Container>
      <Header>
        <Title>{state?.name ? state.name : loading ? 'Loading...' : infoData?.name}</Title>
      </Header>
      {loading ? (
        <Loader>Loading...</Loader>
      ) : (
        <>
          <Overview>
            <OverviewItem>
              <span>Rank:</span>
              <span>{infoData?.rank}</span>
            </OverviewItem>
            <OverviewItem>
              <span>Symbol:</span>
              <span>${infoData?.symbol}</span>
            </OverviewItem>
            <OverviewItem>
              <span>Open Source:</span>
              <span>{infoData?.open_source ? 'Yes' : 'No'}</span>
            </OverviewItem>
          </Overview>
          <Description>{infoData?.description}</Description>
          <Overview>
            <OverviewItem>
              <span>Total Suply:</span>
              <span>{tickerData?.total_supply}</span>
            </OverviewItem>
            <OverviewItem>
              <span>Max Supply:</span>
              <span>{tickerData?.max_supply}</span>
            </OverviewItem>
          </Overview>

          <Tabs>
            <Tab isActive={chartMatch !== null}>
              <Link to={`/${coinId}/chart`}>Chart</Link>
            </Tab>
            <Tab isActive={priceMatch !== null}>
              <Link to={`/${coinId}/price`}>Price</Link>
            </Tab>
          </Tabs>

          <Link to={`/${coinId}/chart`}>Chart</Link>

          <Link to={`/${coinId}/price`}>Price</Link>

          {/* 다양한 URL 로 Switch 하기 */}
          <Switch>
            <Route path={`/${coinId}/price`}>
              <Price />
            </Route>
            <Route path={`/${coinId}/chart`}>
              <Chart />
            </Route>
          </Switch>
        </>
      )}
    </Container>
  );
}

interface RouteState {
  name: string;
}

interface RouteParams {
  coinId: string;
}

interface InfoData {
  id: string;
  name: string;
  symbol: string;
  rank: number;
  is_new: boolean;
  is_active: boolean;
  type: string;
  description: string;
  message: string;
  open_source: boolean;
  started_at: string;
  development_status: string;
  hardware_wallet: boolean;
  proof_type: string;
  org_structure: string;
  hash_algorithm: string;
  first_data_at: string;
  last_data_at: string;
}

interface PriceData {
  id: string;
  name: string;
  symbol: string;
  rank: number;
  circulating_supply: number;
  total_supply: number;
  max_supply: number;
  beta_value: number;
  first_data_at: string;
  last_updated: string;
  quotes: {
    USD: {
      ath_date: string;
      ath_price: number;
      market_cap: number;
      market_cap_change_24h: number;
      percent_change_1h: number;
      percent_change_1y: number;
      percent_change_6h: number;
      percent_change_7d: number;
      percent_change_12h: number;
      percent_change_15m: number;
      percent_change_24h: number;
      percent_change_30d: number;
      percent_change_30m: number;
      percent_from_price_ath: number;
      price: number;
      volume_24h: number;
      volume_24h_change_24h: number;
    };
  };
}

const Container = styled.div`
  padding: 0px 20px;
  max-width: 480px;
  margin: 0 auto;
`;

const Header = styled.header`
  height: 20vh;
  display: flex;
  justify-content: center;
  align-items: center;
`;

const Title = styled.h1`
  font-size: 50px;
  color: ${(props) => props.theme.accentColor};
`;

const Loader = styled.span`
  display: block;
  text-align: center;
`;
const Overview = styled.div`
  display: flex;
  justify-content: space-between;
  background-color: rgba(0, 0, 0, 0.5);
  padding: 10px 20px;
  border-radius: 10px;
`;
const OverviewItem = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
  span:first-child {
    font-size: 10px;
    font-weight: 400;
    text-transform: uppercase;
    margin-bottom: 5px;
  }
`;
const Description = styled.p`
  margin: 20px 0px;
`;

const Tabs = styled.div`
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  margin: 25px 0px;
  gap: 10px;
`;

const Tab = styled.span<{ isActive: boolean }>`
  //props 받기
  text-align: center;
  text-transform: uppercase;
  font-size: 12px;
  font-weight: 400;
  background-color: rgba(0, 0, 0, 0.5);
  padding: 7px 0px;
  border-radius: 10px;
  color: ${(props) => (props.isActive ? props.theme.accentColor : props.theme.textColor)};
  a {
    display: block;
  }
`;
export default Coin;

api.ts

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

export async function fetchCoins() {
  // const response = await fetch(`${BASE_URL}/coins`);
  // const json = await response.json();
  // return json;
  //가독성 높은 오래된 방법
  return fetch(`${BASE_URL}/coins`).then((response) => response.json());
  // 두 코드는 같은 것이다. fetcher 함수인 fetchCoin 은 URL을 부르고 URL로 부터 json을 return 한다.
}

export async function fetchCoinInfo(coinId: string) {
  //coinId 타입 명시 필요, coin Id를 fetch하는 함수
  return fetch(`${BASE_URL}/coins/${coinId}`).then((response) => response.json());
}

export async function fetchCoinTickers(coinId: string) {
  //coinId 타입 명시 필요 coin Ticker를 fetch하는 함수
  return fetch(`${BASE_URL}/tickers/${coinId}`).then((response) => response.json());
}

App.js

import { createGlobalStyle } from 'styled-components';
import Router from './Router';
import { ReactQueryDevtools } from 'react-query/devtools';

export default function App() {
  const GlobalStyle = createGlobalStyle`
  @import url('https://fonts.googleapis.com/css2?family=Archivo+Narrow:wght@500&family=Bebas+Neue&family=Black+Han+Sans&family=Do+Hyeon&family=Source+Sans+Pro:wght@300;400&family=Ubuntu+Mono:ital@1&display=swap');
  html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, menu, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
main, menu, nav, output, ruby, section, summary,
time, mark, audio, video {
  margin: 0;
  padding: 0;
  border: 0;
  font-size: 100%;
  font: inherit;
  vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, main, menu, nav, section {
  display: block;
}
/* HTML5 hidden-attribute fix for newer browsers */
*[hidden] {
    display: none;
}
body {
  line-height: 1;
}
menu, ol, ul, li {
  list-style: none;

}
blockquote, q {
  quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
  content: '';
  content: none;
}
table {
  border-collapse: collapse;
  border-spacing: 0;
}
*{
  box-sizing: border-box;
}
body{
  font-family: 'Source Sans Pro', sans-serif;
  //현재 App은 Theme안에 있으므로 Theme의 props에 접근 가능한 상태이다.
  //그 말은 즉슨 이렇게 쓸 수 있다는 뜻이다
  background-color: ${(props) => props.theme.bgColor};
  color : ${(props) => props.theme.textColor};
}
a{
  text-decoration: none;
  color:inherit;
// 링크가 클릭되었을 때 너무 못생겨져서 부모로부터 상속받게 하여
// 색깔을 유지시켰다
}
  `;
  return (
    <>
      <GlobalStyle />
      {/* 이것이 reset이고 기본값을 제거하는 방법이다. */}

      <Router />
      <ReactQueryDevtools initialIsOpen={true} />
      {/* developer tool */}
    </>
  );
}

좋은 웹페이지 즐겨찾기