Crypto Tracker 4.7 _ Nested Routes part One

Eugenius1st·2022년 3월 12일
0

ReactJS_MasterClass

목록 보기
23/48
post-custom-banner

Nested Routes part One

필요한 데이터를 다 받아 왔으니 Screen Paint 하기

Nested router는 route 안에 있는 또 다른 route 이다.
nested route는 tab을 이용할 때 많은 도움을 준다.

price 나 chart 탭을 선택하면 네이게이션 바 처럼 state나 URL을 활용해서 접속하도록 돕는다.

  1. Price 와 Chart라는 파일을 만든다
  1. Switch 와 Route를 한번 더 사용한다.



    route 안에 또 다른 route를 넣은 것을 알 수 있다.

다음 시간에는 tab을 만드는 것을 알아보자 !

App

import { createGlobalStyle } from 'styled-components';
import Router from './Router';

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 />
    </>
  );
}

Coin

import { useEffect, useState } from 'react';
import { Switch, Route, useLocation, useParams } from 'react-router';
import styled from 'styled-components';
import Chart from './Chart';
import Price from './Price';

//useParams 는 URL에서 관심있어 하는 정보를 잡아낼 수 있게 해준다.

function Coin() {
  const [loading, setLoading] = useState(true);
  //state가 coins으로 이루어진 array라는 것을 알려주기 위해<interface>작성

  //Coins 에서 보내는 state 를 사용하기 위해 useLocation 이라는 hook을 사용해서 받아와보자! react-router-dom이 보내주는
  // location object에 접근하기만 하면 된다.
  const { coinId } = useParams<RouteParams>();
  const { state } = useLocation<RouteState>();
  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);
      //loading 상태를 false로 만드는 것 잊지 말아라
    })();
  }, [coinId]);
  //[] 안에 coinId를 넣어주면 더 좋다
  const [info, setInfo] = useState<InfoData>();
  const [priceInfo, setPriceInfo] = useState<PriceData>();

  return (
    <Container>
      <Header>
        <Title>{state?.name ? state.name : loading ? 'Loading...' : info?.name}</Title>
      </Header>
      {loading ? (
        <Loader>Loading...</Loader>
      ) : (
        <>
          <Overview>
            <OverviewItem>
              <span>Rank:</span>
              <span>{info?.rank}</span>
            </OverviewItem>
            <OverviewItem>
              <span>Symbol:</span>
              <span>${info?.symbol}</span>
            </OverviewItem>
            <OverviewItem>
              <span>Open Source:</span>
              <span>{info?.open_source ? 'Yes' : 'No'}</span>
            </OverviewItem>
          </Overview>
          <Description>{info?.description}</Description>
          <Overview>
            <OverviewItem>
              <span>Total Suply:</span>
              <span>{priceInfo?.total_supply}</span>
            </OverviewItem>
            <OverviewItem>
              <span>Max Supply:</span>
              <span>{priceInfo?.max_supply}</span>
            </OverviewItem>
          </Overview>
          <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;
`;
export default Coin;

Coins

import styled from 'styled-components';
import { Link } from 'react-router-dom';
import { useEffect, useState } from 'react';

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 CoinsList = styled.ul``;

const Coin = styled.li`
  background-color: white;
  color: ${(props) => props.theme.bgColor};
  margin-bottom: 10px;
  padding: 20px;
  border-radius: 15px;

  a {
    padding: 5px; // 좀 더 넓은 범위에서 transition 효과 적용 가능
    transition: color 0.2s ease-in;
  }

  &:hover {
    a {
      color: ${(props) => props.theme.accentColor};
    }
    // 아래에서는 a가 아닌 Link라는 이름으로 사용했지만
    // css에서는 anchor 를 선택해야 했다. 이건 모든 react router link들이
    // 결국에는 anchor로 바뀔거기도 하고,
    // react router dom이 우리 대신 설정을 도와줄 특별한 event listener들이 있기도 하다
  }
`;

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

const Loader = styled.span`
  text-align: center;
  display: block;
`;

const Img = styled.img`
  width: 25px;
  height: 25px;
  margin-right: 10px;
`;

interface CoinInterface {
  id: string;
  name: string;
  symbol: string;
  rank: number;
  is_new: boolean;
  is_active: boolean;
  type: string;
}

function Coins() {
  //state를 이용해 coins를 만들어 준다
  const [coins, setCoins] = useState<CoinInterface[]>([]);
  const [loading, setLoading] = useState(true);
  //state가 coins으로 이루어진 array라는 것을 알려주기 위해<interface>작성
  useEffect(() => {
    (async () => {
      const response = await fetch('https://api.coinpaprika.com/v1/coins');
      const json = await response.json();
      setCoins(json.slice(0, 100));
      setLoading(false);
      // loading 상태 수정하기
    })();
  }, []);

  return (
    <Container>
      <Header>
        <Title>코인</Title>
      </Header>
      {loading ? (
        <Loader>"Loading..."</Loader>
      ) : (
        //loading 이 참이면 Loading... 출력, 거짓이면 CoinsList 보여줌
        <CoinsList>
          {coins.map((coin) => (
            <Coin key={coin.id}>
              <Img
                src={`https://cryptoicon-api.vercel.app/api/icon/${coin.symbol.toLowerCase()}`}
              />

              <Link
                to={{
                  pathname: `/${coin.id}`,
                  state: { name: coin.name },
                  //Link를 이용해 string 이외에 더 많은 데이터를 보낼 수 있다
                }}
              >
                {coin.id}
              </Link>
            </Coin>
          ))}
        </CoinsList>
      )}
    </Container>
  );
}

export default Coins;

Price

function Price() {
  return <h1>Price</h1>;
}

export default Price;

Chart

function Chart() {
  return <h1>Chart</h1>;
}

export default Chart;
profile
최강 프론트엔드 개발자가 되고싶은 안유진 입니다
post-custom-banner

0개의 댓글