react-query
react-query는 useEffect, fetch문을 쉽게 대체할 수 있도록 해준다!
이점
- useState, useEffect, fetch문으로 된 데이터의 fetch 과정을 단순화해준다
- 데이터를 cache에 저장하여 페이지를 되돌아올 경우 fetch를 안하고 저장된 데이터를 보여준다
react의 버전에 따라서 설치가 다르다 아래는 react 18 기준
npm i @tanstack/react-query
index.tsx에 provider 적용 (사용하기 위한 설치 과정 같은 것)
// index.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { ThemeProvider } from "styled-components";
import { lightTheme } from "./theme";
import { QueryClient, QueryClientProvider } from "react-query";
const queryClient = new QueryClient();
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={lightTheme}>
<App />
</ThemeProvider>
</QueryClientProvider>
);
fetcher 함수 만들기
기존 fetch한 내용을 분리하여 ts파일에서 함수로 만들어준다
// api.ts
export async function fetchCoins() {
return fetch("https://api.coinpaprika.com/v1/coins").then((response) =>
response.json()
);
}
// export async function fetchCoins() {
// const response = await fetch("https://api.coinpaprika.com/v1/coins");
// const json = await response.json();
// return json;
// }
기존 구문을 대체하고 useQuery로 대체한다
로딩의 boolean 여부는 isLoading에, fetch된 데이터 정보는 data에 저장됨
import { useQuery } from "react-query";
import { fetchCoins } from "../api";
function Coins() {
const { isLoading, data } = useQuery<ICoin[]>("allCoins", fetchCoins);
// const [coins, setCoins] = useState<ICoin[]>([]);
// 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 (
...
)
}
coin에도 적용
// Coin.tsx
import {
useLocation,
useParams,
Switch,
Route,
Link,
useRouteMatch,
} from "react-router-dom";
import styled from "styled-components";
import { useState, useEffect } from "react";
import Price from "./Price";
import Chart from "./Chart";
import { useQuery } from "react-query";
import { fetchCoinInfo, fetchCoinTickers } from "../api";
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;
`;
const Overview = styled.div`
display: flex;
justify-content: space-between;
background-color: ${(props) => props.theme.textColor};
color: ${(props) => props.theme.bgColor};
padding: 10px 20px;
border-radius: 10px;
`;
const OverviewItem = styled.div`
display: flex;
flex-direction: column;
align-items: center;
span:first-child {
font-size: 10px;
text-transform: uppercase;
margin-bottom: 5px;
}
`;
const Description = styled.div`
margin: 20px 0px;
padding: 10px;
`;
const Tabs = styled.div`
display: grid;
grid-template-columns: repeat(2, 1fr);
margin: 25px 0px;
gap: 10px;
`;
const Tab = styled.span<{ $isActive: boolean }>`
text-align: center;
text-transform: uppercase;
font-size: 12px;
background-color: ${(props) =>
props.$isActive ? props.theme.accentColor : props.theme.textColor};
color: ${(props) => props.theme.bgColor};
padding: 10px 0px;
border-radius: 10px;
a {
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 { 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: tickerLoading, data: tickerData } = useQuery<PriceData>(
["ticker", coinId],
() => fetchCoinTickers(coinId)
);
// const [loading, setLoading] = useState(true);
// const [info, setInfo] = useState<InfoData>();
// const [priceInfo, setPriceInfo] = useState<PriceData>();
// console.log(priceMatch);
// useEffect(() => {
// (async () => {
// const infoData = await (
// await fetch(`https://api.coinpaprika.com/v1/coins/${coinId}`)
// ).json();
// setInfo(infoData);
// const priceData = await (
// await fetch(`https://api.coinpaprika.com/v1/tickers/${coinId}`)
// ).json();
// console.log(priceData);
// setPriceInfo(priceData);
// setLoading(false);
// })();
// }, []);
const loading = infoLoading || tickerLoading;
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 supply</span>
<span>{tickerData?.total_supply}</span>
</OverviewItem>
<OverviewItem>
<span>max supply</span>
<span>{tickerData?.max_supply}</span>
</OverviewItem>
</Overview>
<Tabs>
<Tab $isActive={priceMatch !== null}>
<Link to={`/${coinId}/price`}>price</Link>
</Tab>
<Tab $isActive={chartMatch !== null}>
<Link to={`/${coinId}/chart`}>chart</Link>
</Tab>
</Tabs>
<Switch>
<Route path={`/${coinId}/price`}>
<Price />
</Route>
<Route path={`/:coinId/chart`}>
<Chart />
</Route>
</Switch>
</>
)}
</Container>
);
}
export default Coin;
react-query-devtools
cache에 저장되어 있는 데이터를 보여줌
npm i @tanstack/react-query-devtools
아래 방법으로 적용한다
// App.tsx
import { createGlobalStyle } from "styled-components";
import Router from "./Router";
import { ReactQueryDevtools } from "react-query/devtools";
function App() {
return (
<>
<GlobalStyle />
<Router />
<ReactQueryDevtools initialIsOpen={true} />
</>
);
}
export default App;
'React' 카테고리의 다른 글
[React] 가상화폐시세 사이트 7 - refetch & react-helmet (0) | 2023.08.30 |
---|---|
[React] 가상화폐시세 사이트 6 - apexcharts (0) | 2023.08.30 |
[React] 가상화폐시세 사이트 4 - Nested Router & useRouteMatch (0) | 2023.08.29 |
[React] 가상화폐시세 사이트 3 - fetch data & useLocation (0) | 2023.08.29 |
[React] 가상화폐시세 사이트 2 - Theme 적용 (0) | 2023.08.29 |