Introduction
In this article, I will explain how I solved the Frontend mentor "REST Countries API with a color theme switcher" challenge using Chakra UI and Create React App. At the end of this tutorial, we should be able to
See all countries from the API on the homepage
Search for a country using an input field
Filter countries by region
Click on a country to see more detailed information on a
separate pageClick through to the border countries on the detail page
Toggle the color scheme between light and dark mode
(optional)
Prerequisite
To follow this tutorial, you should have a basic understanding of the following.
Basic knowledge of JavaScript ES6 syntax and features
The basics of ReactJS terminology: JSX, State, Asynchronous
JavaScript, etc.Basic understanding of Restful APIs.
Basic knowledge of TypeScript
Basic understanding of Chakra UI
Basic knowledge of React Router
Demo and Github Links
Component breakdown
- Header Component
- Home Component
- Singlepage Component
Setup
Before building each component, we start by creating a new create-react-app project from a template using Chakra UI automatic typescript template, as in the code below.
# TypeScript using npm
npx create-react-app my-app --template @chakra-ui/typescript
This command will bootstrap the bare-bones react app that is ready to use.
After creating our app, our folder structure should look like what we have in the image below.
Now, we navigate into the my-app folder and start our app by running npm start
. We should have something that looks like the image below.
Removing unwanted CSS
Now that we have generated a create-react-app project using a template, all we need to do is start creating our components and clean up the files.
Within the src folder, I have created two new folders: pages
and components
.
Packages
In addition to our pre-installed packages from create-react-app typescript templates, we will install additional two other packages that are
React-Router:
This will help us with routing within our app.
ChakraIcons:
This will provide a set of commonly used interface icons you can use in our project.
We can now navigate into our root folder and run the code below so you can install both packages together.
npm install react-router-dom@6 @chakra-ui/icons
Navlink component
Here, we use a Chakra UI Navbar template with a user dropdown and a Dark theme switcher.
import {
Box,
Flex,
Button,
useColorModeValue,
Stack,
useColorMode,
} from '@chakra-ui/react';
import { MoonIcon, SunIcon } from '@chakra-ui/icons';
import { useNavigate } from 'react-router-dom';
export default function Nav() {
const { colorMode, toggleColorMode } = useColorMode();
let navigate = useNavigate();
return (
<>
<Box bg={useColorModeValue('gray.100', 'gray.900')} px={4}>
<Flex h={16} alignItems={'center'} justifyContent={'space-between'}>
<Box onClick={()=> navigate('/')} >Where in the world?</Box>
<Flex alignItems={'center'}>
<Stack direction={'row'} spacing={7}>
<Button onClick={toggleColorMode}>
{colorMode === 'light' ? <MoonIcon /> : <SunIcon />}
</Button>
</Stack>
</Flex>
</Flex>
</Box>
</>
);
}
For this component, we are using ReactRouter for easy routing and chakra-UI/icons to access Chakra UI Icons.
Routing:
Here, we link our components to the appropriate pages.
import React from 'react'
import {Route, Routes } from "react-router-dom";
import Home from '../Pages/Home';
import SingleCountry from '../Pages/SingleCountry';
function Routing() {
return (
<div>
<Routes>
<Route path="/" element={<Home/>} />
<Route path="/singlecountry/:countryname" element={<SingleCountry/>} />
</Routes>
</div>
)
}
export default Routing
Now that we have our navbar and routing components, we move to create our Homepage file
Home:
// Importing
import React from "react";
import { useState, useEffect } from "react";
import {
Flex,
GridItem,
Image,
Input,
InputGroup,
InputLeftElement,
Select,
SimpleGrid,
Spacer,
} from "@chakra-ui/react";
import { Box } from "@chakra-ui/react";
import { useNavigate } from "react-router-dom";
import { Progress } from "@chakra-ui/react";
import { SearchIcon } from "@chakra-ui/icons";
import Nav from "../Components/Navlink";
function Home() {
//States
const [data, setData] = useState([]);
const [data2, setData2] = useState([]);
const [searchInput, setSearchInput] = useState("");
const [selectInput, setSelectInput] = useState("all");
let navigate = useNavigate();
//Calling Apis
useEffect(() => {
if (selectInput === "all") {
fetch(`https://restcountries.com/v3.1/all`)
.then((res) => res.json())
.then((data) => {
return (
setData(data),
setData2(data))
})
.catch((err) => console.log("Error:", err.message));
} else {
fetch(`https://restcountries.com/v3.1/region/${selectInput}`)
.then((res) => res.json()).then((data)=>{
return (
setData(data),
setData2(data)
)
})
.catch((err) => console.log("Error:", err.message));
}
}, [selectInput]);
//Handle Region select
const handleChangeSelect = (e) => {
setSelectInput(e.target.value);
};
//Handle Country Search
const handleChangeInput = (e) => {
e.preventDefault();
setSearchInput(e.target.value);
setData(
data2.filter((x) =>
x?.name?.common
?.toLowerCase()
?.includes(e?.target?.value?.toLowerCase())
)
);
};
return (
<div>
{/* Navbar */}
<Nav/>
{/*
Country Search and Region Select form */}
<form>
<Flex pr="50" pl="50" flexWrap={"wrap"}>
<Box p="4">
<InputGroup>
<InputLeftElement
pointerEvents="none"
children={<SearchIcon color="gray.300" />}
/>
<Input
value={searchInput}
onChange={handleChangeInput}
type="text"
placeholder="Search for a country "
/>
</InputGroup>
</Box>
<Spacer />
<Box p="4">
<Select onChange={handleChangeSelect} placeholder="Select option">
<option value="all">All</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>
</Box>
</Flex>
</form>
{/* Data Rendering */}
{data2?.length === 0 ? (
<Progress colorScheme="pink" size="xs" isIndeterminate />
) : (
<Box w="100%">
<SimpleGrid
columns={[1, null, 4]}
spacing={10}
pt="100"
pr="50"
pl="50"
>
{data?.map((x) => (
<GridItem
key={x?.name?.common}
onClick={() =>
navigate(`/singlecountry/${x?.cca2?.toLowerCase()}`, {})
}
>
<Box
maxW="sm"
borderWidth="1px"
borderRadius="lg"
overflow="hidden"
>
<Image
src={x?.flags?.svg}
alt={x?.name?.common}
height="200px"
width="100%"
/>
<Box p="6">
<Box
mt="1"
fontWeight="semibold"
as="h4"
lineHeight="tight"
noOfLines={1}
>
{x?.name?.common}
</Box>
<Box
mt="1"
fontWeight="semibold"
as="h4"
lineHeight="tight"
noOfLines={1}
>
Population: {x?.population}
</Box>
<Box
mt="1"
fontWeight="semibold"
as="h4"
lineHeight="tight"
noOfLines={1}
>
Region: {x?.region}
</Box>
<Box
mt="1"
fontWeight="semibold"
as="h4"
lineHeight="tight"
noOfLines={1}
>
Capital: {x?.capital}
</Box>
</Box>
</Box>
</GridItem>
))}
</SimpleGrid>
</Box>
)}
</div>
);
}
export default Home;
As you can see, there are a lot of lines of code to comprehend at once, so let's take it one step at a time.
Importing all the necessary components
Here, We import Flex, grid, item, Image, Input, InputGroup, InputLeftElement,Select,SimpleGrid,Spacer,Box, Progress from Chakra UI, use navigate from react-router, SearchIcon from ChakraIcons, and Nav from our Navlinks component
// Importing
import React from "react";
import { useState, useEffect } from "react";
import {
Flex,
GridItem,
Image,
Input,
InputGroup,
InputLeftElement,
Select,
SimpleGrid,
Spacer,
Box,
Progress
} from "@chakra-ui/react";
import { useNavigate } from "react-router-dom";
import { SearchIcon } from "@chakra-ui/icons";
import Nav from "../Components/Navlink";
States:
We declared our states here
//States
const [data, setData] = useState([]);
const [data2, setData2] = useState([]);
const [searchInput, setSearchInput] = useState("");
const [selectInput, setSelectInput] = useState("all");
let navigate = useNavigate();
Api:
Calling Api
//Calling Apis
useEffect(() => {
if (selectInput === "all") {
fetch(`https://restcountries.com/v3.1/all`)
.then((res) => res.json())
.then((data) => {
return (
setData(data),
setData2(data))
})
.catch((err) => console.log("Error:", err.message));
} else {
fetch(`https://restcountries.com/v3.1/region/${selectInput}`)
.then((res) => res.json()).then((data)=>{
return (
setData(data),
setData2(data)
)
})
.catch((err) => console.log("Error:", err.message));
}
}, [selectInput]);
Region selection and Country Search functions
//Handle Region select
const handleChangeSelect = (e) => {
setSelectInput(e.target.value);
};
//Handle Country Search
const handleChangeInput = (e) => {
e.preventDefault();
setSearchInput(e.target.value);
setData(
data2.filter((x) =>
x?.name?.common
?.toLowerCase()
?.includes(e?.target?.value?.toLowerCase())
)
);
};
Import Navbar
<Nav/>
Country Search and Region Select form
<form>
<Flex pr="50" pl="50" flexWrap={"wrap"}>
<Box p="4">
<InputGroup>
<InputLeftElement
pointerEvents="none"
children={<SearchIcon color="gray.300" />}
/>
<Input
value={searchInput}
onChange={handleChangeInput}
type="text"
placeholder="Search for a country "
/>
</InputGroup>
</Box>
<Spacer />
<Box p="4">
<Select onChange={handleChangeSelect} placeholder="Select option">
<option value="all">All</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>
</Box>
</Flex>
</form>
Data Rendering
{data2?.length === 0 ? (
<Progress colorScheme="pink" size="xs" isIndeterminate />
) : (
<Box w="100%">
<SimpleGrid
columns={[1, null, 4]}
spacing={10}
pt="100"
pr="50"
pl="50"
>
{data?.map((x) => (
<GridItem
key={x?.name?.common}
onClick={() =>
navigate(`/singlecountry/${x?.cca2?.toLowerCase()}`, {})
}
>
<Box
maxW="sm"
borderWidth="1px"
borderRadius="lg"
overflow="hidden"
>
<Image
src={x?.flags?.svg}
alt={x?.name?.common}
height="200px"
width="100%"
/>
<Box p="6">
<Box
mt="1"
fontWeight="semibold"
as="h4"
lineHeight="tight"
noOfLines={1}
>
{x?.name?.common}
</Box>
<Box
mt="1"
fontWeight="semibold"
as="h4"
lineHeight="tight"
noOfLines={1}
>
Population: {x?.population}
</Box>
<Box
mt="1"
fontWeight="semibold"
as="h4"
lineHeight="tight"
noOfLines={1}
>
Region: {x?.region}
</Box>
<Box
mt="1"
fontWeight="semibold"
as="h4"
lineHeight="tight"
noOfLines={1}
>
Capital: {x?.capital}
</Box>
</Box>
</Box>
</GridItem>
))}
</SimpleGrid>
</Box>
)}
Single Country Component
Now, we create the single country component.
When a user clicks on a country, this is the single detailed information page that opens on a separate page. All we are doing here is setting our states and then updating the state with the response gotten from the API inside use effect. We then map our country state inside Chakra components.
import React, { useEffect, useState } from "react";
import { useParams, useNavigate } from "react-router-dom";
import {
Button,
Center,
GridItem,
Image,
Progress,
SimpleGrid,
} from "@chakra-ui/react";
import { Box } from "@chakra-ui/react";
import Nav from "../Components/Navlink";
function SingleCountry() {
let { countryname } = useParams();
const [data, setData] = useState();
let navigate = useNavigate();
useEffect(() => {
fetch(`https://restcountries.com/v3.1/alpha/${countryname}`)
.then((res) => res.json())
.then((data) => setData(data))
.catch((err) => console.log("Error:", err.message));
}, [countryname]);
return (
<div>
<Nav />
<Box onClick={() => navigate(-1)} p={'10'} >
<Button size="lg" variant="solid" mr="3">
Back
</Button>
</Box>
{data === undefined || data === null ? (
<Progress colorScheme="pink" size="xs" isIndeterminate />
) : (
data?.map((x) => {
return (
<Center key={x?.name?.common} >
<SimpleGrid
columns={[1, null, 2]}
spacing={100}
pt="100"
pr="50"
pl="50"
>
<GridItem w="100%">
<Image src={x?.flags?.svg} alt={x?.Region} height="350" />
</GridItem>
<GridItem w="100%">
<Box
mt="1"
fontWeight="semibold"
as="h4"
lineHeight="tight"
noOfLines={1}
>
{x?.name?.common}
</Box>
<SimpleGrid columns={2} spacing={10}>
<Box>Native Name: {x?.name?.common}</Box>
<Box>Top Level Domain: {x?.tld[0]}</Box>
</SimpleGrid>
<SimpleGrid columns={2} spacing={10}>
<Box>Population: {x?.population}</Box>
<Box>
Currencies:{" "}
{x?.currencies[Object?.keys(x?.currencies)[0]]?.name}
</Box>
</SimpleGrid>
<SimpleGrid columns={2} spacing={10}>
<Box>Region: {x?.region}</Box>
<Box>
Language(s): {x?.languages[Object.keys(x?.languages)[0]]}
</Box>
</SimpleGrid>
<SimpleGrid columns={2} spacing={10}>
<Box>Subregion: {x?.subregion}</Box>
</SimpleGrid>
<SimpleGrid columns={2} spacing={10}>
<Box>Capital: {x?.capital}</Box>
</SimpleGrid>
<SimpleGrid mt="50" columns={2} spacing={10}>
<Box>Border Countries:</Box>
<Box>
{x?.borders?.map((x) => (
<Button
onClick={() => navigate(`/singlecountry/${x}`)}
size="lg"
key={x}
variant="solid"
mr="3"
>
{x}
</Button>
))}
</Box>
</SimpleGrid>
</GridItem>
</SimpleGrid>
</Center>
);
})
)}
</div>
);
}
export default SingleCountry;
Letโs create the heart of this project.
App.tsx
import * as React from "react"
import { ChakraProvider, theme } from '@chakra-ui/react'
import { BrowserRouter } from "react-router-dom";
import Routing from "./Components/Routing";
export const App = () => (
<div>
<ChakraProvider theme={theme}>
<BrowserRouter>
<Routing/>
</BrowserRouter>
</ChakraProvider>
</div>
)
Having created all our components, let's enter our app's directory and run npm start
to start the app. At this point, we should see something like the image below.
At this point, if we click on any of the countries, it should route us to a different page where we can see more details about the country, like the image below.
Well, congrats on that great hustle! You have the solution to REST Countries API with a color theme switcher ready at your disposal.
Thanks for Reading๐๐
It's great to see that you have enjoyed the article. Please, let me know what you think in the comment section.
On to another blog, some other day, till then Femi๐.
Top comments (0)