Covid Map - React project - day 2
April 25, 2021, 9:42 p.m.
Yesterday I started a project using React, Leaflet.js and Open Disease Data API. In this series, I'm writing about things I'm doing and problems I'm encountering while creating this project.
Part 1: Covid map - React project - day 1
Things I've done on day 2:
* Fetched data from disease.sh about each country
* Formatted data into GeoJSON
* Displayed a marker for each country with a popup that contains basic data
Problems I've encountered and my solutions:
1. I wanted to use another approach for storing data from an API and decided to create a custom useFetch hook.
While building a *custom hook* we are extracting component logic into a reusable function. So a custom hook is placed in a separate file in the src folder and it needs to start with *use* and it also has the ability to call other hooks.
In the useFetch function, I'm passing URL as a parameter and I'm using useState and useEffect with Fetch API. Almost the same as if I was fetching the data inside App.js or any component.
The function returns 3 elements: data from API, loading and error.
//useFetch.js
import { useState, useEffect } from 'react';
const useFetch = (url) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const res = await fetch(url);
const json = await res.json();
setData(json)
setLoading(false)
} catch (error) {
console.log(`Failed to fetch countries: ${error.message}`, error)
setError(error)
}
};
fetchData()
}, [url])
return { data, loading, error}
}
export default useFetch
The next step is to access the data, loading and error in the Map.js component.
//Map.js
import useFetch from '../useFetch';
const Mapt = () => {
const url = 'https://disease.sh/v3/covid-19/countries'
const { data, loading, error } = useFetch(url)
console.log(data)
if (error) return <p>Error!</p>;
if (loading) return <p>Loading...</p>;
return (
<MapContainer></MapContainer>
)
}
export default Map
At the moment I'm not using any data yet.
2. To display the data on a map I needed to format them into GeoJSON.
What is GeoJSON?
From Wikipedia:
"GeoJSON is an open standard format designed for representing simple geographical features, along with their non-spatial attributes. It is based on the JSON format."
On Leaflet.js we can find an example code of GeoJSON
// from Leaflet.js
var geojsonFeature = {
"type": "Feature",
"properties": {
"name": "Coors Field",
"amenity": "Baseball Stadium",
"popupContent": "This is where the Rockies play!"
},
"geometry": {
"type": "Point",
"coordinates": [-104.99404, 39.75621]
}
};
Now I need to do the same with my own data. At first, I was trying to create this GeoJSON in my Map.js file. But I was wrong. It needs to be done in the useFetch hook, just after fetching the response from API.
So I'm creating a geoJson object with the type "FeatureCollection". Because API contains hundreds of arrays I need to loop through all of them using `map()` to have access to those features.
// useFetch.js
// ...
try {
const res = await fetch(url);
const json = await res.json();
const geoJson = {
type: "FeatureCollection",
features: json.map((country = {}) => {
const { countryInfo = {}} = country;
const { lat, long: lng} = countryInfo;
return {
type: "Feature",
properties: {
...country,
},
geometry: {
type: "Point",
coordinates: [lat, lng]
}
}
})
}
setData(geoJson)
setLoading(false)
}
// ...
Thanks to it I'm having access to all data from properties as well as coordinates. Those pairs of latitude (lat) and longitude(lng) are one per country.
3. Now I can access the data in `Map.js`.
I'm using a ternary operator to check if there are any data and if data exists it's displaying the markers and popups, otherwise it should show nothing.
const Map = () => {
// I won't be rewriting the whole code only the part in which I'm displaying the Markers
// ...
return (
<MapContainer>
{data ? data.features.map(country => {
return (
<Marker icon={redIcon} position={country.geometry.coordinates} key={country.properties.country}>
<Popup>
<h2>{country.properties.country}</h2>
<p>Cases: {country.properties.cases}</p>
<p>Deaths: {country.properties.deaths}</p>
<p>Recovered: {country.properties.recovered}</p>
<hr />
<p>Cases Today: {country.properties.todayCases}</p>
<p>Death Today: {country.properties.todayDeaths}</p>
<p>Recovered Today: {country.properties.todayRecovered}</p>
<hr />
<p>Last Update: {country.properties.updated}</p>
</Popup>
</Marker>
)
})
: null}
</MapContainer>
// ...
)
}
export default Map
I'm aware that my Popups aren't clean. It can be done better.
At the moment the map looks like this: