TypeScript를 사용할 때는 데이터를 가지고 올 때, 어떤 형태의 (interface) 데이터인지 알려줘야 한다!
coin의 interface 지정
interface CoinInterface {
id: string;
name: string;
symbol: string;
rank: number;
is_new: boolean;
is_active: boolean;
type: string;
}
useState에서 coin의 interface 지정
function Coins() {
const [coins, setCoins] = useState<CoinInterface[]>([])
return ...
}
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();
setCoins(json.slice(0, 100));
setLoading(false);
})();
}, []);
최종 코드
// Coins.tsx
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: 10vh;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 20px;
`;
const CoinsList = styled.ul``;
const Coin = styled.li`
background-color: ${(props) => props.theme.textColor};
color: ${(props) => props.theme.bgColor};
border-radius: 15px;
margin-bottom: 10px;
a {
padding: 20px;
transition: color 0.15s 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();
setCoins(json.slice(0, 100));
setLoading(false);
})();
}, []);
return (
<Container>
<Header>
<Title>Coin</Title>
</Header>
{loading ? (
<Loader>Loading...</Loader>
) : (
<CoinsList>
{coins.map((coin) => (
<Coin key={coin.id}>
<Link to={`/${coin.id}`}>{coin.name} →</Link>
</Coin>
))}
</CoinsList>
)}
</Container>
);
}
export default Coins;
useLocation
이전 페이지에 있던 데이터를 가져올 수 있다!
React Router Dom v5 Link Docs
Link에서 가져올 데이터를 state에 적어준다
<Link
to={{
pathname: `/${coin.id}`,
state: { name: coin.name },
}}
>
{coin.name} →
</Link>
옮겨진 페이지에서 state안에 object 형태로 있는 것을 확인할 수 있다!
// Coin.tsx
const location = useLocation();
console.log(location);
// {pathname: '/btc-bitcoin', search: '', hash: '', state: {…}, key: 'qpimth'}
// hash: ""
// key: "qpimth"
// pathname: "/btc-bitcoin"
// search: ""
// state: {name: 'Bitcoin'}
// [[Prototype]]: Object
해당 주소를 직접 입력하여 이전 페이지의 데이터가 없는 상태로 페이지가 render 될 경우에 location 정보는 없다.
따라서 해당 경우도 고려해야한다
interface RouteState {
name: string;
}
function Coin() {
const { state } = useLocation<RouteState>();
return <Title>{state?.name || "Loading"}</Title>;
}
최종 코드
// Coin.tsx
import { useLocation, useParams } from "react-router-dom";
import styled from "styled-components";
import { useState, useEffect } from "react";
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;
margin-bottom: 20px;
`;
const Title = styled.h1`
font-size: 48px;
color: ${(props) => props.theme.accentColor};
`;
const Loader = styled.span`
text-align: center;
display: block;
`;
interface RouteParams {
coinId: string;
}
interface RouteState {
name: string;
}
// interface ITag {
// id: string;
// name: string;
// coin_counter: number;
// ico_counter: number;
// }
interface InfoData {
id: string;
name: string;
symbol: string;
rank: number;
is_new: boolean;
is_active: boolean;
type: string;
logo: string;
// tags: ITag[];
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;
};
};
}
function Coin() {
const { coinId } = useParams<RouteParams>();
const [loading, setLoading] = useState(true);
const { state } = useLocation<RouteState>();
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();
console.log(infoData);
const priceData = await (
await fetch(`https://api.coinpaprika.com/v1/tickers/${coinId}`)
).json();
console.log(priceData);
setLoading(false);
})();
}, []);
return (
<Container>
<Header>
<Title>{state?.name || "Loading"}</Title>
</Header>
{loading ? <Loader>Loading...</Loader> : null}
</Container>
);
}
export default Coin;