기존 useState와 useEffect를 사용한 코드에서 React Query를 사용하여 좀 더 효율적으로 데이터를 관리할 수 있도록 리팩토링 해보려고 한다.
기존에는 useState
와 useEffect
를 사용하여 데이터를 가져오고, coins와 loading의 상태를 직접 관리하고 있다.
// Coins.tsx
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);
})();
}, []);
React Query
를 사용하기 위해 먼저 QueryClient
를 생성하고, QueryClientProvider
로 App
을 감싸주어, 앱 전체에서 React Query
를 사용할 수 있도록 한다.
// index.ts
import { QueryClient, QueryClientProvider } from "react-query";
.
.
.
const queryClient = new QueryClient();
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
api.ts
파일을 생성하여, fetchCoins
함수를 정의한다.
// api.ts
export function fetchCoins() {
return fetch("https://api.coinpaprika.com/v1/coins").then((response) =>
response.json()
);
}
기존 코드를 리팩토링하여 useQuery
훅을 사용하도록 변경하였다.
// Coins.tsx
const { isLoading, data } = useQuery<ICoin[]>("allCoins", fetchCoins);
fetcher
함수와 함께 useQuery
를 사용하면 코드를 단순화할 수 있다.
useQuery:
데이터 로딩 상태를 자동으로 관리하며, 데이터를 가져오는 작업을 단순화한다.isLoading:
함수가 실행중이라면 true, 종료되면 false값을 리턴한다.data:
함수가 종료되면 해당 함수의 데이터를 data에 저장한다.캐싱:
React Query는 데이터를 자동으로 캐시하여, 화면에 재진입할 때 새로운 api 호출을 방지하고, 캐시된 데이터를 빠르게 불러온다.Coin.tsx도 동일하게 리팩토링 해보자! 그런데 이번에는 useQuery가 두 개이기 때문에 추가적으로 설정해줘야 할 부분이 있다.
// Coin.tsx
const [info, setInfo] = useState<InfoData>();
const [priceInfo, setPriceInfo] = useState<PriceData>();
const [loading, setLoading] = useState(true);
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]);
// api.ts
export function fetchCoinInfo(coinId: string) {
return fetch(`${BASE_URL}/coins/${coinId}`).then((response) =>
response.json()
);
}
export function fetchCoinPriceInfo(coinId: string) {
return fetch(`${BASE_URL}/tickers/${coinId}`).then((response) =>
response.json()
);
}
// Coin.tsx
const { isLoading: infoLoading, data: infoData } = useQuery<InfoData>(
["info", coinId],
() => fetchCoinInfo(coinId || "")
);
const { isLoading: priceLoading, data: priceData } = useQuery<PriceData>(
["price", coinId],
() => fetchCoinPriceInfo(coinId || "")
);
["info", coinId]
, ["price", coinId]
useQuery는 각각의 고유한 key를 가지고 있어야 하기 때문에, 다른 쿼리와 혼동되지 않도록 key를 각각 설정해준다.{ isLoading: infoLoading, data: infoData }
, { isLoading: priceLoading, data: priceData }
isLoading과 data도 동일한 이름을 갖고 있어 각 쿼리의 로딩 상태와 데이터를 각각 따로 관리할 수 있도록 위와 같이 설정해준다.