
API 를 호출하고 받은 응답값을 화면에 보여주는 과정에서 타입스크립트를 사용해봅시다.
country.ts
export type Country = {
name: {
common: string;
};
capital: string[];
flags: {
png: string;
};
};
export type SelectedCountry = Country & {
isSelected: boolean;
};
worldApi.ts
import axios from 'axios';
import { Country } from '../types/country';
const WORLD_API = "https://restcountries.com/v3.1/all";
export const getCountries = async (): Promise<Country[]> => {
try {
const response = await axios.get<Country[]>(WORLD_API);
return response.data;
} catch (error) {
throw new Error('Failed to fetch countries data');
}
}
CountryCard.tsx
import React from "react";
import { SelectedCountry } from "../types/country";
interface CountryCardProps {
country: SelectedCountry; // SelectedCountry 타입
handleSelect: (country: SelectedCountry) => void; // 부모 컴포넌트에서 전달된 함수
}
const CountryCard: React.FC<CountryCardProps> = ({ country, handleSelect }) => {
return (
<li
key={country.name.common}
onClick={() => handleSelect(country)}
className="flex-col drop-shadow-lg border rounded-lg m-4 p-4"
>
<img
src={country.flags.png}
alt={`${country.name.common} flag`}
className="w-1/2 h-1/2 m-auto object-cover"
/>
<div>
<h3 className="text-lg font-medium mt-4">{country.name.common}</h3>
<p className="text-sm">{country.capital}</p>
</div>
</li>
);
};
export default CountryCard;
CountryList.tsx
import React, { useEffect, useState } from "react";
import { SelectedCountry } from "../types/country";
import { getCountries } from "../api/worldApi";
import { AxiosError } from "axios";
import CountryCard from "./CountryCard";
const CountryList: React.FC = () => {
const [countries, setCountries] = useState<SelectedCountry[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [error, setError] = useState<AxiosError | null>(null);
useEffect(() => {
const fetchCountries = async () => {
try {
const data = await getCountries();
const selectedCountries = data.map((country) => ({
...country,
isSelected: false,
}));
setCountries(selectedCountries);
} catch (error) {
if (error instanceof AxiosError) {
setError(error);
} else {
console.error(error);
}
} finally {
setIsLoading(false);
}
};
fetchCountries();
}, []);
const handleSelect = (selectedCountry: SelectedCountry) => {
setCountries((prevCountries) =>
prevCountries.map((country) =>
country.name.common === selectedCountry.name.common
? { ...country, isSelected: !country.isSelected }
: country
)
);
};
if (isLoading) {
return <div>로딩중...</div>;
}
if (error) {
console.error(error);
return <div>에러가 발생했습니다!!</div>;
}
return (
<div className="w-3/4 flex flex-col items-center m-auto gap-2">
<h1 className="font-semibold text-2xl">Favorite Countries</h1>
<ul className="grid lg:grid-cols-4 md:grid-cols-3 sm:grid-cols-2">
{countries
.filter((country) => country.isSelected)
.map((country) => (
<CountryCard
key={country.name.common}
country={country}
handleSelect={handleSelect}
/>
))}
</ul>
<h1 className="font-semibold text-2xl">Countries</h1>
<ul className="grid lg:grid-cols-4 md:grid-cols-3 sm:grid-cols-2">
{countries
.filter((country) => !country.isSelected)
.map((country) => (
<CountryCard
key={country.name.common}
country={country}
handleSelect={handleSelect}
/>
))}
</ul>
</div>
);
};
export default CountryList;
App.tsx
import CountryList from "./components/CountryList";
function App() {
return (
<div className="w-full flex">
<CountryList />
</div>
);
}
export default App;