Building a Country Finder Application with React
Abhishek Gurjar
Posted on September 13, 2024
Introduction
In this blog post, we'll explore how to build a Country Finder application using React. This application allows users to search for countries, filter them by region, and view detailed information about each country. We will utilize React's hooks and context to manage state and theming, and we'll integrate with the REST Countries API to fetch country data.
Project Overview
The Country Finder application provides an interactive interface where users can:
- Search for countries by name.
- Filter countries by region.
- View detailed information about each country, including its flag, population, and more.
Features
- Search Bar: Allows users to search for countries by name.
- Filter by Region: Dropdown menu to filter countries based on their region.
- Country Details: Displays detailed information about a selected country.
- Theme Toggle: Switch between light and dark themes.
Technologies Used
- React: JavaScript library for building user interfaces.
- REST Countries API: Provides data about countries.
- CSS: For styling the application.
- React Router: For navigating between pages and passing state.
Project Structure
The project is organized into several components:
-
App.js: Main component that includes the
Header
andOutlet
for routing. - Header.js: Displays the application title and theme toggle button.
- Home.js: Main page with search and filter options, and a list of countries.
- SearchBar.js: Component for searching countries.
- SelectMenu.js: Dropdown menu for filtering countries by region.
- CountriesList.js: Displays a list of countries based on the search and filter criteria.
- CountryCard.js: Displays a summary of each country.
- CountryDetail.js: Shows detailed information about a selected country.
- CountryDetailShimmer.js: Loading placeholder for country details.
- Error.js: Error handling component for routes.
Installation
- Clone the repository:
git clone https://github.com/abhishekgurjar-in/country-finder.git
cd country-finder
- Install dependencies:
npm install
- Start the development server:
npm start
Usage
- Search for a Country: Enter a country name in the search bar to filter the list of countries.
- Filter by Region: Select a region from the dropdown menu to see countries in that region.
- View Details: Click on a country card to view detailed information about the country.
Code Explanation
App.js
The App
component wraps the Header
and Outlet
components within a ThemeProvider
, managing the theme state across the application.
import Header from "./components/Header";
import { Outlet } from "react-router-dom";
import "./App.css";
import { ThemeProvider } from "./contexts/ThemeContext";
const App = () => {
return (
<ThemeProvider>
<Header />
<Outlet />
</ThemeProvider>
);
};
export default App;
Header.js
The Header
component allows users to toggle between light and dark themes and displays the application title.
import { useTheme } from "../hooks/useTheme"
export default function Header() {
const [isDark, setIsDark] = useTheme();
return (
<header className={`header-container ${isDark ? 'dark' : ''}`}>
<div className="header-content">
<h2 className="title">
<a href="/">Country Finder</a>
</h2>
<p className="theme-changer" onClick={() => {
setIsDark(!isDark);
localStorage.setItem('isDarkMode', !isDark);
}}>
<i className={`fa-solid fa-${isDark ? 'sun' : 'moon'}`} />
{isDark ? 'Light' : 'Dark'} Mode
</p>
</div>
</header>
)
}
Home.js
The Home
component contains the search bar, filter menu, and lists the countries based on the search and filter criteria.
import React, { useState } from 'react';
import SearchBar from './SearchBar';
import SelectMenu from './SelectMenu';
import CountriesList from './CountriesList';
import { useTheme } from '../hooks/useTheme';
export default function Home() {
const [query, setQuery] = useState('');
const [isDark] = useTheme();
return (
<main className={`${isDark ? 'dark' : ''}`}>
<div className="search-filter-container">
<SearchBar setQuery={setQuery} />
<SelectMenu setQuery={setQuery} />
</div>
<CountriesList query={query} />
</main>
)
}
SearchBar.js
The SearchBar
component handles user input for searching countries.
import React from 'react';
export default function SearchBar({ setQuery }) {
return (
<div className="search-container">
<i className="fa-solid fa-magnifying-glass"></i>
<input
onChange={(e) => setQuery(e.target.value.toLowerCase())}
type="text"
placeholder="Search for a country..."
/>
</div>
)
}
SelectMenu.js
The SelectMenu
component provides a dropdown for filtering countries by region.
import React from 'react';
export default function SelectMenu({ setQuery }) {
return (
<select className="filter-by-region" onChange={(e) => setQuery(e.target.value.toLowerCase())}>
<option hidden>Filter by Region</option>
<option value="Africa">Africa</option>
<option value="Americas">Americas</option>
<option value="Asia">Asia</option>
<option value="Europe">Europe</option>
<option value="Oceania">Oceania</option>
</select>
)
}
CountriesList.js
The CountriesList
component fetches and displays a list of countries.
import React, { useEffect, useState } from 'react';
import CountryCard from './CountryCard';
import CountriesListShimmer from './CountriesListShimmer';
export default function CountriesList({ query }) {
const [countriesData, setCountriesData] = useState([]);
useEffect(() => {
fetch('https://restcountries.com/v3.1/all')
.then((res) => res.json())
.then((data) => {
setCountriesData(data);
});
}, []);
if (!countriesData.length) {
return <CountriesListShimmer />;
}
return (
<div className="countries-container">
{countriesData
.filter((country) =>
country.name.common.toLowerCase().includes(query) || country.region.toLowerCase().includes(query)
)
.map((country) => (
<CountryCard
key={country.name.common}
name={country.name.common}
flag={country.flags.svg}
population={country.population}
region={country.region}
capital={country.capital?.[0]}
data={country}
/>
))}
</div>
)
}
CountryDetail.js
The CountryDetail
component fetches and displays detailed information about a selected country.
import React, { useEffect, useState } from 'react';
import { Link, useLocation, useParams } from 'react-router-dom';
import { useTheme } from '../hooks/useTheme';
import CountryDetailShimmer from './CountryDetailShimmer';
import './CountryDetail.css';
export default function CountryDetail() {
const [isDark] = useTheme();
const params = useParams();
const { state } = useLocation();
const countryName = params.country;
const [countryData, setCountryData] = useState(null);
const [notFound, setNotFound] = useState(false);
function updateCountryData(data) {
setCountryData({
name: data.name.common || data.name,
nativeName: Object.values(data.name.nativeName || {})[0]?.common,
population: data.population,
region: data.region,
subregion: data.subregion,
capital: data.capital,
flag: data.flags.svg,
tld: data.tld,
languages: Object.values(data.languages || {}).join(', '),
currencies: Object.values(data.currencies || {})
.map((currency) => currency.name)
.join(', '),
borders: [],
});
if (!data.borders) {
data.borders = [];
}
Promise.all(
data.borders.map((border) =>
fetch(`https://restcountries.com/v3.1/alpha/${border}`)
.then((res) => res.json())
.then(([borderCountry]) => borderCountry.name.common)
)
).then((borders) => {
setTimeout(() =>
setCountryData((prevState) => ({ ...prevState, borders }))
);
});
}
useEffect(() => {
if (state) {
updateCountryData(state);
return;
}
fetch(`https://restcountries.com/v3.1/name/${countryName}?fullText=true`)
.then((res) => res.json())
.then(([data]) => {
if (!data) {
setNotFound(true);
} else {
updateCountryData(data);
}
})
.catch(() => setNotFound(true));
}, [countryName, state]);
if (notFound) {
return (
<div className={`error-container ${isDark ? 'dark' : ''}`}>
<h3>Country not found</h3>
<Link to="/">Back to home</Link>
</div>
);
}
if (!countryData) {
return <CountryDetailShimmer />;
}
return (
<div className={`country-detail-container ${isDark ? 'dark' : ''}`}>
<Link to="/" className="back-button">
<i className="fa-solid fa-arrow-left" />
Back
</Link>
<div className="country-detail-content">
<img src={countryData.flag} alt={`${countryData.name} flag`} />
<div className="country-detail-info">
<h1>{countryData.name}</h1>
<div className="details">
<p><strong>Native Name:</strong> {countryData.nativeName}</p>
<p><strong>Population:</strong> {countryData.population}</p>
<p><strong>Region:</strong> {countryData.region}</p>
<p><strong>Subregion:</strong> {countryData.subregion}</p>
<p><strong>Capital:</strong> {countryData.capital}</p>
<p><strong>Top Level Domain:</strong> {countryData.tld}</p>
<p><strong>Languages:</strong> {countryData.languages}</p>
<p><strong>Currencies:</strong> {countryData.currencies}</p>
<p><strong>Border Countries:</strong> {countryData.borders.join(', ') || 'None'}</p>
</div>
</div>
</div>
</div>
);
}
CountryDetailShimmer.js
The CountryDetailShimmer
component shows a loading placeholder while fetching country details.
import React from 'react';
export default function CountryDetailShimmer() {
return (
<div className="country-detail-shimmer">
<div className="shimmer-img"></div>
<div className="shimmer-info">
<div className="shimmer-line name"></div>
<div className="shimmer-line"></div>
<div className="shimmer-line"></div>
<div className="shimmer-line"></div>
<div className="shimmer-line"></div>
</div>
</div>
);
}
CountryCard.js
The CountryCard
component displays a brief overview of each country.
import React from 'react';
import { Link } from 'react-router-dom';
export default function CountryCard({ name, flag, population, region, capital, data }) {
return (
<div className="country-card">
<img src={flag} alt={`${name} flag`} />
<h3>{name}</h3>
<p><strong>Population:</strong> {population}</p>
<p><strong>Region:</strong> {region}</p>
<p><strong>Capital:</strong> {capital}</p>
<Link to={`/country/${name}`} state={data}>
<button>More Details</button>
</Link>
</div>
);
}
CountriesListShimmer.js
The CountriesListShimmer
component shows a loading placeholder while fetching the list of countries.
import React from 'react';
export default function CountriesListShimmer() {
return (
<div className="countries-list-shimmer">
{Array.from({ length: 10 }).map((_, index) => (
<div key={index} className="shimmer-card"></div>
))}
</div>
);
}
Live Demo
You can view a live demo of the Country Finder application by visiting Country Finder Demo.
Conclusion
In this project, we built a Country Finder application using React that allows users to search for countries, filter them by region, and view detailed information. We integrated with the REST Countries API and used React's hooks and context to manage state and theming.
Credits
- React: React
- REST Countries API: REST Countries
- Font Awesome: Font Awesome
Author
Abhishek Gurjar is a dedicated web developer passionate about creating practical and functional web applications. Check out more of his projects on GitHub.
Posted on September 13, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.