
#5.0 ~ 5.4 2023.04.04 (화)
섹션4는 React router 버전6에 관한 내용이고,이 강의에서는 버전5를 사용하기 때문에 먼저 버전5로 만든다음, 추후에 버전6로 리팩토링 할 예정임. 아직 많은 프로젝트들이 버전5 클래식 버전을 사용하고 있으니까 사용해도 아무 문제 없음.
이번 섹션에서는 지금까지 배운 TS,스타일 컴포넌트, 리액트의 Router Dom을 이용하며 평범하게 데이터를 fetching 한다음 react-query로 바꾸는 연습도 하면서 간단한 코인 트래킹 어플리케이션을 만들어 볼거임.
시작하기전 먼저 Set up을 해보자!
npm i react-router-dom@5.3.0 react-query
npm i --save-dev @types/react-router-dom
위의 명령어로 React-router와 React-query를 설치해줌.그리고 타입스크립트는 react-router-dom이 뭔지를 모르니 @types도 설치해주자.
페이지 라우팅은 다음과 같이 할것.
/ -> 전체 코인들
/:id -> /btc -> 코인 디테일 페이지
/btc/information -> 코인 디테일 페이지에서 정보 페이지로
/btc/chart -> 코인 디테일 페이지에서 차트 페이지로

src폴더 안에 routes폴더를 만들어주고 첫번째 스크린인 Coins.tsx파일과 디테일 페이지인 Coin.tsx파일을 만들어줌.
import { BrowserRouter, Switch, Route } from "react-router-dom";
import Coin from "./routes/Coin";
import Coins from "./routes/Coins";
function Router() {
return (
<BrowserRouter>
<Switch>
<Route path="/:coinId">
<Coin />
</Route>
<Route path="/">
<Coins />
</Route>
</Switch>
</BrowserRouter>
);
}
export default Router;
src폴더 안에 Router.tsx파일을 만들어서 위와 같이 react-router-dom을 세팅 해줌.

App.tsx에 위와 같이 Router를 렌더링 해줌.

coin.tsx파일에서 URL의 파라미터 부분을 잡아내기 위해서 useParams()를 사용했는데 타입스크립트는 useParams()를 빈 오브젝트라고 생각해서 오류를 내고 있음. 
const {coinId} = useParams<{coinId:string}>();로 대체 가능함.
이번엔 CSS set up을 해보자. 아무것도 건들지 않은 CSS는 위와 같이 브라우저의 기본 스타일을 가지고 있는데 (h1이 기본 font-size를 가지거나 List가 padding을 가지는것 등등) 이걸 다 없앨 거임.
import { Reset } from 'styled-reset'를 설치해서 <Reset/>컴포넌트를 사용하는 방법도 있지만 Reset CSS를 이용해 전체 document에 적용해 볼거임.
그렇다면 스타일 컴포넌트에서 전체 document에 적용 시키려면 어떻게 해야 할까?
//App.tsx
import { createGlobalStyle } from "styled-components";
import Router from "./Router";
const GlobalStyle = createGlobalStyle`
`;
function App() {
return (
<>
<GlobalStyle></GlobalStyle>
<Router></Router>
</>
);
}
export default App;
createGlobalStyle이란 프로퍼티를 갖고 있음. <GlobalStyle/>컴포넌트는 렌더링 될 때, 전역 스코프에 스타일들을 적용시켜줌.<> </> : Fragment라고 불리며 일종의 유령 컴포넌트임. 2개의 컴포넌트를 리턴해야 할 때 사용함. 부모 없이 서로 붙어 있는 많은 것들을 리턴할 수 있음.import { createGlobalStyle } from "styled-components";
import Router from "./Router";
const GlobalStyle = createGlobalStyle`
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, 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,
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, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
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;
}
`;
function App() {
return (
<>
<GlobalStyle></GlobalStyle>
<Router></Router>
</>
);
}
export default App;

이번 강의에서는 어플리케이션에 들어갔을때 가장 먼저 보이는 Coins screen을 만들어 보자. 코인리스트를 만들거임.
//coins.tsx
import { Link } from "react-router-dom";
import styled from "styled-components";
const Container = styled.div`
padding: 0px 20px;
`;
const Header = styled.header`
height: 10vh;
display: flex;
justify-content: center;
align-items: center;
`;
const CoinList = styled.ul``;
const Coin = styled.li`
background-color: white;
color: ${(props) => props.theme.bgColor};
margin-bottom: 10px;
border-radius: 15px;
a {
padding: 20px;
transition: color 0.2s ease-in;
display: block;
}
&:hover {
a {
color: ${(props) => props.theme.accentColor};
}
}
`;
const Title = styled.h1`
font-size: 48px;
color: ${(props) => props.theme.accentColor};
`;
const coins = [
{
id: "btc-bitcoin",
name: "Bitcoin",
symbol: "BTC",
rank: 1,
is_new: false,
is_active: true,
type: "coin",
},
{
id: "eth-ethereum",
name: "Ethereum",
symbol: "ETH",
rank: 2,
is_new: false,
is_active: true,
type: "coin",
},
{
id: "hex-hex",
name: "HEX",
symbol: "HEX",
rank: 3,
is_new: false,
is_active: true,
type: "token",
},
];
function Coins() {
return (
<Container>
<Header>
<Title>코인</Title>
</Header>
<CoinList>
{coins.map((coin) => (
<Coin key={coin.id}>
<Link to={`/${coin.id}`}>{coin.name} →</Link>
</Coin>
))}
</CoinList>
</Container>
);
}
export default Coins;
<a> 태그를 쓰면 새로고침되어 버리기 때문에 react-router-dom의 Link 컴포넌트를 사용해서 link를 걸어줌.display:block 해줌.App.tsx로 가서a{color:inherit;}로 바꿔줌.
index.tsx 에서 렌더 내부의 <React.StrictMode> 를 div로 바꿔 해결했다. 댓글을 보니 react-router v6는 문제가 없다고 하였고, v5의 문제인 것 같았다.결과화면 ↓

이번 강의에서는 API로부터 데이터를 fetch(가져오기)해보자. 데이터가 5000개를 받아오는데 100개로 줄일 거임.
//Coins.tsx
const Container = styled.div`
padding: 0px 20px;
max-width: 480px;
margin: 0 auto;
`;
먼저 화면을 크게 했을 때도, 모바일 화면처럼 화면을 가운데에 위치하기 위해서 max-width와 margin을 추가해 줌.
결과화면 ↓

//Coins.tsx
interface CoinInterface {
id: string;
name: string;
symbol: string;
rank: number;
is_new: boolean;
is_active: boolean;
type: string;
}
// {
// id: "hex-hex",
// name: "HEX",
// symbol: "HEX",
// rank: 3,
// is_new: false,
// is_active: true,
// type: "token",
// }, 원래 데이터의 모습을 바탕으로 interface를 만듦
//Coins.tsx
interface CoinInterface {
id: string;
name: string;
symbol: string;
rank: number;
is_new: boolean;
is_active: boolean;
type: string;
}
function Coins() {
const [coins, setCoins] = useState<CoinInterface[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
(() => console.log(1))();
}, []);
return (
<Container>
<Header>
<Title>코인</Title>
</Header>
<CoinList>
{coins.map((coin) => (
<Coin key={coin.id}>
<Link to={`/${coin.id}`}>{coin.name} →</Link>
</Coin>
))}
</CoinList>
</Container>
);
}
export default Coins;
useState()를 만들어서 state안에다가 초기값을 빈 배열로 적용해 주고, 타입스크립트에게 interface를 이용해 coins State는 coins로 이루어진 array라고 알려줌. import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import styled from "styled-components";
const Container = styled.div`
padding: 0px 20px;
max-width: 480px;
margin: 0 auto;
`;
const Header = styled.header`
height: 10vh;
display: flex;
justify-content: center;
align-items: center;
`;
const CoinList = styled.ul``;
const Coin = styled.li`
background-color: white;
color: ${(props) => props.theme.bgColor};
margin-bottom: 10px;
border-radius: 15px;
a {
padding: 20px;
transition: color 0.2s ease-in;
display: block;
}
&:hover {
a {
color: ${(props) => props.theme.accentColor};
}
}
`;
const Title = styled.h1`
font-size: 48px;
color: ${(props) => props.theme.accentColor};
`;
const Loader = styled.span`
text-align: center;
display: block;
`;
interface CoinInterface {
id: string;
name: string;
symbol: string;
rank: number;
is_new: boolean;
is_active: boolean;
type: string;
}
function Coins() {
const [coins, setCoins] = useState<CoinInterface[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
(async () => {
const response = await fetch("https://api.coinpaprika.com/v1/coins");
const json = await response.json();
//console.log(json)
setCoins(json.slice(0, 100));
setLoading(false);
})();
}, []);
console.log(coins);
return (
<Container>
<Header>
<Title>코인</Title>
</Header>
{loading ? (
<Loader>"Loading..."</Loader>
) : (
<CoinList>
{coins.map((coin) => (
<Coin key={coin.id}>
<Link to={`/${coin.id}`}>{coin.name} →</Link>
</Coin>
))}
</CoinList>
)}
</Container>
);
}
export default Coins;



Loading 컴포넌트를 false일땐 코인리스트가 보이도록 만들어줌.이제 api를 이용해 coins 스크린(홈페이지)의 코인아이콘 이미지들을 불러와보자.

<Img/> 스타일 컴포넌트를 만들어서 src에 url을 써주고, url맨 뒤에는 coin.id를 넣어줘서 코인들의 각각의 이미지를 불러오게 함. Coins.tsx 페이지는 완성했으니 이제 coin.tsx 페이지를 만들어 볼거임.Coins.tsx페이지에서 api를 불러 데이터를 받아오고 있고 개별 코인 페이지로 화면이동을 하면 parameter를 이용해 url로 개별 코인 정보(coin.id)를 넘기고 있는데, 이러면 개별 코인 페이지에서도 api를 불러오게 되어 이 과정에서 user들은 오직 loading만 보는 문제가 있음.
<Link/> 컴포넌트를 이용해 object형식의 state를 개별 코인 페이지로 넘겨줌. useLocation()으로 state를 받아옴.{state?.name || "Loading..."}로 state의 name이 있으면 name을 보여주고 없으면 Loading string을 보이도록 해서 해결함.결과화면↓

#5.5 ~ 5.7 2023.04.07 (금)
이번 강의에서는 개별 코인 상세페이지에서 보여줄 데이터를 불러 올거임.
사용할 URL은 아래와 같이 2가지임.
https://api.coinpaprika.com/v1/coins/btc-bitcoin
https://api.coinpaprika.com/v1/tickers/btc-bitcoin
첫번째 URL에서는 필요한 정보를 얻어올 거고, 두번째 URL에서는 코인의 정보와 가격들을 얻어올 거임.

useParams()로 갖고 있음.//coin.tsx
function Coin() {
const [loading, setLoading] = useState(true);
const { coinId } = useParams<Routeparams>();
const { state } = useLocation<RouteState>();
// console.log(state); {name: 'Bitcoin'}
useEffect(() => {
()();
}, []);
return (
<Container>
<Header>
<Title>{state?.name || "Loading..."}</Title>
</Header>
{loading ? <Loader>"Loading..."</Loader> : null}
</Container>
);
}
export default Coin;
const response = await (
await fetch(`https://api.coinpaprika.com/v1/coins/${coinId}`)
).json();
response와 json이라는 2개의 변수를 받은거임.

info와 priceInfo는 타입스크립트가 빈 object라고 생각함.이번 강의에서는 info와 priceInfo를 빈 object라고 생각하는 타입스크립트에게 데이터를 설명해 줄 거임.


info와 priceInfo의 interface를 만들건데 먼저 console.log()로 데이터를 받아오는것을 확인하고 info 데이터의 오른쪽 마우스를 눌러서 나오는 Store object as global variable을 클릭하면 object데이터가 temp1에 저장됨.temp1에 접근할 수 있음.priceInfo도 temp2에 저장해줌.interface IInfoData {}
interface IPriceData {}
✔️ 댓글참고
VSCode 단축키
Ctrl(Command)+D: 같은 문자열 선택
Shift+Alt(Option)+i: 선택한 모든 문자열에 가장 우측 끝으로 포커싱
Ctrl(Command)+Shift+오른쪽 화살표: 현재 선택한 문자열을 기준으로 우측 끝까지 문자열 선택

Object.keys()를 사용하여 temp1의 key값들을 다 모아서 스트링으로 만듦.


Object.values()를 사용하여 temp1의 vlaue값들을 다 모아서 map()함수로 type으로 바꾼다음 스트링으로 만듦. 

Shift+Option+i 으로 커서가 오른쪽으로 포커싱되면 value값들을 붙여넣기 해줌.


temp2도 똑같은 방법으로 하면됨.//coin.tsx
import { useEffect, useState } from "react";
import { useLocation, useParams } from "react-router-dom";
import styled from "styled-components";
const Title = styled.h1`
font-size: 48px;
color: ${(props) => props.theme.accentColor};
`;
const Loader = styled.span`
text-align: center;
display: block;
`;
const Container = styled.div`
padding: 0px 20px;
max-width: 480px;
margin: 0 auto;
`;
const Header = styled.header`
height: 10vh;
display: flex;
justify-content: center;
align-items: center;
`;
interface Routeparams {
coinId: string;
}
interface RouteState {
name: string;
}
interface InfoData {
id: string;
name: string;
symbol: string;
rank: number;
is_new: boolean;
is_active: boolean;
type: string;
logo: 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;
links: object;
links_extended: object;
whitepaper: object;
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;
};
};
}
function Coin() {
const [loading, setLoading] = useState(true);
const { coinId } = useParams<Routeparams>();
const { state } = useLocation<RouteState>();
const [info, setInfo] = useState<InfoData>();
const [priceInfo, setPriceInfo] = useState<PriceData>();
// console.log(state); {name: 'Bitcoin'}
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);
console.log(infoData);
console.log(priceData);
})();
}, []);
return (
<Container>
<Header>
<Title>{state?.name || "Loading..."}</Title>
</Header>
{loading ? <Loader>"Loading..."</Loader> : null}
</Container>
);
}
export default Coin;
댓글 참고 ↓
JSON데이터를 타입스크립트 타입으로 빠르게 변환시켜주는 사이트
https://app.quicktype.io/?l=ts
//coin.tsx
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);
console.log(infoData);
console.log(priceData);
})();
}, []);
시작하기전, API로부터 데이터를 request한 후에 loading이 false가 되도록 setLoading(false);을 해주고,

coinID의 의존성을 잃어버렸다고 말하고 있음.
coinId 변수를 추가해주면 경고가 사라짐.no dependencies([])를 사용해야 하는걸 알고 있을거임.coinId라고 불리는 것을 사용하고 있다고 알려주는 거임.coinId가 변하면 동작할텐데, coinId는 url에 위치해서 절대 변하지 않으므로 넣어도 됨.
<Price/>와 <Chart/>로 이루어진 2개의 탭도 만들거임.
loading ? "Loading..." : info?.name부분은 홈페이지로 부터 온게 아닌 경우에 실행될거임.(path가 / 가 아닌 다른 url로 들어온 경우)Price.tsx와 Chart.tsx 파일을 만들어줌.<Price/>컴포넌트와 <Chart/>컴포넌트가 렌더링 되게 만듦.⭐️ Nested router(중첩라우터)란 route안에 있는 또 다른 route임. 웹사이트에서 탭을 사용할때나 스크린 안에 많은 섹션이 나뉘어진 곳에서 유용하게 쓰임.


?문법은 만약 priceInfo가 없거나 undefined이거나 존재하지 않으면 total_supply에 대해 요구하지 않는 거임.?를 붙이지 않는다면 total_supply를 항상 요구하게 되어서 priceInfo가 undefined라면 undefined안에 total_supply가 없다는 에러가 날거임.
btc-bitcoin/price url로 들어가면 컴포넌트가 보임.

btc-bitcoin/chart url로 들어가면 컴포넌트가 보임.
#5.8 ~ 5.12 (토)
price와 chart를 스위치하는 탭을 만들어 보자.
//Coin.tsx
<Link to={`/${coinId}/chart`}>Chart</Link>
<Link to={`/${coinId}/price`}>Price</Link>
<Switch>
<Route path={`/:coinId/price`}>
<Price />
</Route>
<Route path={`/:coinId/chart`}>
<Chart />
</Route>
</Switch>
Link 컴포넌트를 만들어서 rerender 되지 않게 하고, 클릭하면 chart URL 또는 price URL로 가게 만들어서 탭을 만듦.useRouteMatch()를 이용해서 불러올 수 있음!
useRouteMatch() 훅을 이용해 선택한 url에 접속했을때 정보를 오브젝트로 넘겨받을 수 있음. ()안에 들어간 url이 아닐때는 null을 넘겨줌.
color: ${(props) =>props.isActive ? props.theme.accentColor : props.theme.textColor}; 로 선택한 탭은 accentColor로 그렇지 않은 탭은 기본 textColor로 설정해 줌.탭 결과화면 ↓

아래의 명령어로 설치해 줌. (react query 공식페이지 참고)
npm i react-query (17버전)
npm i @tanstack/react-query (18버전)
//index.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { QueryClient, QueryClientProvider } from "react-query";// import 해오기
import { ThemeProvider } from "styled-components";
import App from "./App";
import { theme } from "./theme";
const queryClient = new QueryClient(); //1
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<div>
<QueryClientProvider client={queryClient}> //2
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
</QueryClientProvider>
</div>
);
index.tsx 파일에서 위와 같이 세팅해 줌.<ThemeProvider/>와 같은 맥락으로 <QueryClientProvider/> 안에 있는 모든 것은 queryClient에 접근할 수 있음.react query에 대해서 배워보자.
coin.tsx (코인 상세 페이지)

loading과 info는 각각 로딩과 데이터를 위한 state임. 그리고 데이터가 준비되면 데이터를 state에 넣고 로딩은 false넣는 과정을 우리 스스로 했음. react query는 이 모든 과정을 자동으로 해줄 거임!coins.tsx (홈페이지)

1. 먼저 데이터를 fetch하는 fetcher 함수가 필요한데, API와 관련된 것들은 component들과 멀리 떨어져 있는게 좋으니 src/api.ts 파일을 만들어서 fetcher 함수를 만들어줌.
위와 같이 fetcher 함수는 fetch promise를 꼭 return 해 줘야함. await / async 대신 promise를 사용함.

2. coins.tsx에 원래 있던 state와 useEffect를 주석처리하고 react query의 useQuery 훅으로 대체함.
useQuery는 2가지 argument가 필요한데, 첫 번째는 queryKey 이며 우리 query의 고유식별자임. 이름은 마음대로 지어도 무관함.() 두 번째 argument에는 fetcher 함수를 넣으면 됨.
isLoading에서 boolean 값으로 알려주고, fetcher 함수가 끝나면 json값을 data에 넣음.
4. 원래 리턴되던 loading state를 useQuery에서 오는isLoading으로 바꾸고, coins state를 data로 바꿔줌. 하지만 타입스크립트는 data가 뭔지 몰라 coin에 오류를 나타냄.

5. 위와 같이 interface로 정의해주면 타입스크립트는 data가 CoinInterface array거나 undefined인걸 알게됨. 위와 같은 오류는 data?로 하면 없어짐.
data는 약 5만개의 모든 데이터를 가져오기 때문에 slice를 이용함.

코인 상세 페이지(coin.tsx)를 react query로 리팩토링 해보자!

react query를 시각화 하기 위해서 Devtools(Developer Tools)라는 것을 사용할 수 있는데, react query에 있는 devtools를 import 해오면 캐시에 있는 query를 볼 수 있음.
npm i -D @tanstack/react-query-devtools
강의에서는 Devtools를 사용하기 위해서 따로 설치하지 않았지만 전 단계에서 @tanstack/react-query 모듈을 설치했으므로 위의 명령어로 devtool을 따로 설치해줌.
이제 src/App.tsx 파일에 가서 Devtools를 세팅하자.
//App.tsx
import { ReactQueryDevtools } from "react-query/devtools"; //1
// import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; -> 오류남
import { createGlobalStyle } from "styled-components";
import Router from "./Router";
const GlobalStyle = createGlobalStyle`
//코드생략
`;
function App() {
return (
<>
<GlobalStyle></GlobalStyle>
<Router></Router>
<ReactQueryDevtools initialIsOpen={true} /> {/* //2 */}
</>
);
}
export default App;
No QueryClient set, use QueryClientProvider to set one이라는 오류가 떴는데import { useQuery } from "react-query";import { ReactQueryDevtools } from "react-query/devtools"; @tanstack/붙어있는것을 위와 같이 바꾸니까 해결됨!
세팅이 완료되면 아래와 같이 Devtools가 나옴.

코인 상세 페이지(coin.tsx)로 가서

api.ts파일로 가서 fetcher함수 2개를 만들어 주자



coin.tsx로와서 원래 있던 state들과 fetch들을 주석처리하고 useQuery를 만듦.const {} = useQuery(["info",coinId], () => fetchCoinInfo(coinId));
const {} = useQuery(["tickers",coinId], () => fetchCoinTickers(coinId));
각각 다른 이름으로 바꿔줌.
const {isLoading:infoLoading , data:infoData} = useQuery(["info",coinId], () => fetchCoinInfo(coinId));
const {isLoading:tickersLoading, data:tickersData } = useQuery(["tickers",coinId], () => fetchCoinTickers(coinId));


const loading = infoLoading || tickersLoading;으로 정의해줌.


이제 코인 상세 페이지 -> 홈페이지 -> 코인 상세 페이지로 다시 들어와도 loading이 뜨지 않는걸 볼 수 있음!
이제 코인 상세 페이지에서 차트를 클릭하면 제일 마지막 주의 시각화 차트를 보여주게 만들거임.
src/routes/Chart.tsx파일로 가서

비트코인 페이지에 들어가면 비트코인의 차트를 보여줘야 함. 유저가 보고 있는 암호화폐를 알아내는 2가지 방법이 있는데 하나는 위와같이 useParams()를 사용하는 것임.
하지만 코인 상세 페이지에선 chart를 렌더링하고 있고 URL로부터 이미 coinId값을 가지고 있기 때문에 코인 상세 페이지에서 props를 보내는 방법을 쓸 수 있음.

//Chart.tsx
interface ChartProps {
coinId: string;
}
function Chart({ coinId }: ChartProps) {
return <h1>Chart</h1>;
}
export default Chart;

api.ts로 가서 fetcher 함수를 하나 만들어줌.
chart.tsx로 가서 useQuery()를 만들어줌. fetcher함수에는 coinId가 필요하므로 argument에 coinID를 넣어줌.
interface IHistorical {
time_open: number;
time_close: number;
open: string;
high: string;
low: string;
close: string;
volume: string;
market_cap: number;
}
interface ChartProps {
coinId: string;
}
function Chart({ coinId }: ChartProps) {
const { isLoading, data } = useQuery<IHistorical[]>(["ohlcv", coinId], () =>
fetchCoinHistory(coinId)
);
결과화면 ↓
