I started learning React earlier this year and took a long pause from it because of some personal issues. I am back on it and decided to document some of my learning processes.
This is a personal react learning journal but if you learn from it, awesome😁
Using React hooks, I would be creating a simple country search app(No CSS used). This project is part of the fullstackopen.com exercises.
Configuration
First off, we create our react project using
npx create-react-app dataforcountry
The app name is dataforcountry
, and npx
is one of the ways to create a react app.
After the react app has been successfully created, we navigate to the folder
cd dataforcountry
In the src
folder, we can see some starter files installed for us by react, we can delete the unnecessary ones and leave only index.js
and app.js
(CSS files are deleted because I do not intend to style this app for now).
Open the app.js
file and also delete the content.
We start the app by running
npm start
A blank page shows up because we have deleted most of the startup content.
The index.js
file should look like this
Fetch data from API
We have to fetch data for the app from an API using axios
, axios
is installed by running
npm install axios
After installation, open the APP.js file, import react
, useState
and useEffect
from react, import axios from axios
import react, {useState, useEffect} from 'react'
import axios from 'axios'
Fetch data from the API using useEffect
and axios
, for this project we would be using data from [https://restcountries.com/](https://restcountries.com/)
API.
const App=()=>{
useEffect(()=>{
axios.get(https://restcountries.com/v3.1/all)
.then(response)=>{
console.log(response.data)
}
},[])
}
Open the console to view the data fetched from the API. This data need to be shown on the page so we create a state using useState
hook and initialize it to an empty array.
const [countries, setCountries] = useState([])
Then we go back to the useEffect
to update the state to the data gotten from the API
const App=()=>{
useEffect(()=>{
axios.get(https://restcountries.com/v3.1/all)
.then(response)=>{
setCountries(response.data)
}
},[])
}
if you log countries
to the console, you would see an object with over 250 countries and their details. we have gotten our data, so now we need to display them on our app and not on the console.
Display data
We have to loop over the countries
array to display them and we do this using the map method. The map
method would return the individual country.
const App=()=>{
useEffect(()=>{
axios.get(https://restcountries.com/v3.1/all)
.then(response)=>{
setCountries(response.data)
}
},[])
return(
<>
{countries.map(country=><h1>{country.name.common}</h1>)}
</>
)
}
Make sure to thoroughly go through and API data so as to know what to render, the name of all countries can be found in a nested object. The app would throw an error if you just use country.name
Now the page displays all country names from the API.
Create Individual Country component
React is known for its's reusable components and frankly creating different components for your project also makes it neater.
We want our app to also display the country's capital, population, languages, flag, and some other things.
In the src
folder, we create a folder named Components and create a file called Country.js
.
In the App.js
file, as we map through countries
, instead of returning <h1>{country.name.common}</h1>
we return <Country/>
We are going to get an error compiling because we haven't exported Country.js
and we haven't imported it into our App.js
, so let's go ahead and do that.
In App.js
we import Country
,
import Country from './Components/Country'
Open the Country.js
file, import react as we did in the app.js, write the Country
component as a function, and export it.
import react from 'react'
const Country = () =>{
return()
}
export default Country
For us to access the data from the API into our Country
component, we have to pass them as a prop
.
Since we are mapping through the array, we have to add a unique key
. Usually, the APIs provide IDs that can be used as keys, There's no unique ID on this so I'll use country.cca3
.
<Country key={country.cca3} country={country}/>
country
can now be accessed from the Country
Component and we can add as much data as we want.
import react from 'react'
const Country = ({country}) =>{
return(
<>
<h2>{country.name.common}</h2>
<p>Capital: {country.capital}</p>
<p>Population: {country.population}</p>
<p>Continent: {country.continents}</p>
<p>Timezone:{country.timezones} </p>
<img src={country.flags.png} alt='' />
</>
)
}
export default Country
Hiccups
One of the challenges I faced while working on displaying data from the endpoint was accessing the language object, the language object had different numbers for each country and it was an object inside of an object so doing country.languages
could not work, and I also could not do country.languages[a specific language]
because that brought out undefined for a lot of countries.
I tried to use Object.values(country.languages)
to get the values of the languages object and then used forEach
to loop and return them but this kept throwing an error, in fact, I got stuck at this point.
I tried to map through country.languages
and got an error that it wasn't a function even though when I logged country.languages
to the console I got an object with each country's language displaying.
I googled implementing loop on components to see other methods I could use and came across this article that helped me in solving my problem and accessing languages for all countries.
Looping through nested objects in react
In Country.js
, we create a variable called language
and assign it to an empty array, then we loop through country.languages
and push it to the empty variable. language
is then used in the component.
import React from 'react';
const Country = ({ country }) => {
const languages = country.languages;
const language = [];
for (const lang in languages) {
language.push(<li key={language.length + 1}>{languages[lang]}</li>);
}
return (
<>
<h2>{country.name.common}</h2>
<p>Capital: {country.capital}</p>
<p>Population: {country.population}</p>
<p>Continent: {country.continents}</p>
<p>Timezone:{country.timezones} </p>
<h3>Languages</h3>
<ul>{language}</ul>
<img src={country.flags.png} alt='' />
</>
);
};
export default Country;
Remember we have to give a unique key as we loop through and since there is no ID
provided we can use the length of the array + 1
, which would generate a unique number after each loop.
Search Component
We create a Search
component just like the Country
Component.
In App.js
, we add the component just above the Country
component, we pass it two props, onChange
and value
.
We also create a new state to hold the input of the search.
const [search, setSearch] = useState('')
<Search onChange={handleSearch} value={search}/>
The handleSearch
function handles the search input and it is passed into the onChange
.
const handleSearch = (e)=>{
setSearch(e.target.value)
}
In the Search Component
, we pass the props down
import React from 'react';
const Search = ({ onChange, value }) => {
return (
<>
<div>
find countries <input onChange={onChange} value={value} type='search' />
</div>
</>
);
};
export default Search;
On the display page, we can see the search input field, we can also type inside but nothing happens. Now we need to make our component respond to our search queries. This can be done by using the filter method.
In App.js
,
const filteredSearch = countries.filter((country) =>
country.name.common.toLowerCase().includes(search.toLowerCase())
);
console.log(filteredSearch);
This filters the countries' names to display only those countries that their name are part of the search query being keyed in. Even with this code, our search feature doesn't still work and that is because we need to map through this filteredSearch
instead of countries
.
In App.js
, we change
{countries.map(country=><Country key={country.cca3} country={country} />)}
to
{filteredSearch.map(country=><Country key={country.cca3} country={country} />)}
Now, this would return searched countries with all their details.
Conditional rendering
The Search feature works but now we want to add some conditions to control the display.
The first one is - If there are too many (over 10) countries that match the query, then the user is prompted to make their query more specific.
The second one is - If there are ten or fewer countries, but more than one, then only country names matching the query are shown
We have to create another component called CountryName
to render only country names on the app.
The third is - When there is only one country matching the query, then the basic data of the country, its flag, and the languages, are shown
We also want the data to be shown only when a search is being made, therefore the page should have just the search input when it loads.
To achieve all these, we are going to use a chained ternary expression.
We check to see if the search
state is empty, if true we return an empty array.
{search === ' ' ? [ ] : }
then we execute our first condition on the list, checking to see if the length of filtered search
is above 10 — if true, it returns a string telling user to use another filter.
{search === ' ' ? [ ]
: filteredSearch.length > 10
? <p>Too many searches, specify another filter</p>
}
This is an incomplete ternary expression and would result in an error, but we aren't done, we still have to fix our second and third conditions.
{search === ' ' ? [ ]
: filteredSearch.length > 10
? <p>Too many searches, specify another filter</p>
: filteredSearch.map((country)=>
filteredSearch.length <= 10 && filteredSearch.length > 1
? <CountryName key={country.cca3} country={country} />
: <Country key={country.cca3} country={country} />
)
}
Simply put, if the search
state is empty render nothing on the page, otherwise if the number of countries displayed after a search is more than 10 render a text telling the user to specify another filter — if it is less than 10 but greater than 1 then display country names only, if it is just one then display the country name with its details.
Other features would be added to this project later, If there are better or simpler ways to achieve this please drop a comment, also pardon all grammatical errors 😃
Link to repo