Tutorial: How to create a global  vaccinations dashboard using React, Mapbox, and Bootstrap
Tags: react, mapbox, bootstrap, visualization
Published: 2021-05-14 12:14:41

Hey everyone! In this tutorial, we shall be building a map-cum-dashboard tracking the progress of vaccination against COVID-19 in every country. 

[Here](https://map-of-vaccinations.netlify.app/) is a preview of our finished React App. I took inspiration from [John Hopkins' COVID-19 Map](https://coronavirus.jhu.edu/map.html) for this project. I am sure most of you guys have seen their Coronavirus map at some point last year. We shall be building something very similar here but instead of COVID-19 cases, we will be looking at vaccination figures.

## :link: Links
- [Deployed site](https://map-of-vaccinations.netlify.app/)
- [GitHub repo](https://github.com/saket2508/global-vaccination-dashboard)
- [React-map-gl docs](https://visgl.github.io/react-map-gl/)
- [Create a Mapbox account here](https://www.mapbox.com/)
- [COVID API 1](https://disease.sh/v3/covid-19/countries)
- [COVID API 2](https://disease.sh/v3/covid-19/vaccine/coverage/countries?lastdays=1&fullData=false)

## Tutorial

I have divided this tutorial into seven sections. I'm linking them here so it becomes easier to follow along and navigate.
1. [Initial Setep](#section1)
2. [Setting up Mapbox](#section2)
3. [Styling + Creating a dashboard header](#section3)
4. [Getting Vaccination figures from the API](#section4)
5. [Creating markers on the map for every country](#section5)
6. [Setting variable size markers](#section6)
7. [Creating styled tooltips](#section7)

### 1. Initial setup <a name="section1"></a>

We are going to start by creating a blank react app by running the create-react-app command. For this tutorial, I am going to name my app, _vaccinations-dashboard_ but feel free to give it any name you like. Run the following lines in your terminal one at a time started.

```
npx create-react-app vaccinations-dashboard
cd vaccinations-dashboard
npm install react-map-gl@5.2.11
npm start
```
*React-map-gl* is a react wrapper for using Mapbox. It has all the necessary components your react app needs for displaying the map provided by the Mapbox API. Note that we are installing an older version of react-map-gl since the newer releases have some glitches rendering the map in production. You can read about this issue [here](https://github.com/visgl/react-map-gl/issues/1266) in case you're interested.

Once you've finished running these lines in your terminal, your React app should be up and running on localhost:3000.

Next, we can remove most of the boilerplate code by clearing everything in `App.js`, `App.css`, and `index.css`.

We shall be writing our own CSS here, and don't worry, it's not going to be too big since we'll be using Bootstrap-5 for styling the navbar and other components. The only bit of CSS we are going to write here is for positioning the map window and styling the tooltip card and circular markers. 

### 2. Setting up Mapbox <a name="section2"></a>

Now, head over to [Mapbox](https://www.mapbox.com/) for creating your account. You are going to need one for using their services. 

Once logged in, you should see be able to see your free access token. Copy this string and save it in a new `.env` file in your root directory. You can give it any name of your choice but make sure it is preceded by REACT_APP. I am going with REACT_APP_MAPBOX for this tutorial. This is going to be referenced by `process.env.<<your_variable_name>>` from `App.js` when you create a Mapbox instance. 

Your .env file should look like this now.

```
REACT_APP_MAPBOX = <<your_access_token>>
``` 

Next, we are going to create a Mapbox instance in our `App.js` file. You can design your own map theme or select one from their [gallery](https://www.mapbox.com/gallery/). The theme that I am using here can be found on this [link](https://api.mapbox.com/styles/v1/saket2000/ckolf18ga1lxq17l31rw3lrxk.html?fresh=true&title=copy&access_token=pk.eyJ1Ijoic2FrZXQyMDAwIiwiYSI6ImNrb2doc2hjcTB3ZmkydXIwcXljeWEyOGoifQ.bKsUSuYbHZ9z8cC5_P_MBw). You can copy it to your account. 

Here is the full code for setting up Mapbox. 

_App.js_
```javascript
import React, { useState, useEffect } from "react";
import ReactMapGL, { Marker, Popup } from 'react-map-gl';

function App(){
   const [viewport, setViewport] = useState({
    width: "100vw",
    height: "100vh",
    latitude: 0,
    longitude: 0,
    zoom: 2
  });

return(
 <ReactMapGL
    {...viewport}
    mapboxApiAccessToken={process.env.REACT_APP_MAPBOX}
    onViewportChange={nextViewport => setViewport(nextViewport)}     
    mapStyle="<<your_map_style>>"
    >   
 </ReactMapGl>
)
}

export default App;
```
On refreshing your dev server, you should see a map window on your screen. The viewport also gets adjusted when you zoom in.

### 3. Styling + Creating a dashboard header <a name="section3"></a>

Next up, we copy the Bootstrap-5 CDN file into `index.html` and import a custom font for our App. I am using Nunito Sans but you can pick any font of your choice from [Google Fonts](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=&cad=rja&uact=8&ved=2ahUKEwifgLXGs8jwAhXCfn0KHRcaCEIQFjAAegQIAhAE&url=https%3A%2F%2Ffonts.google.com%2F&usg=AOvVaw1aUw_UCxsfhIArr7_iyMIi).

copy the following into your `index.css` file

_index.css_
```css
@import url('https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200;0,300;0,400;0,600;1,300&display=swap');

/* body style */
body {
  margin: 0;
  font-family: 'Nunito', sans-serif;
}

/* header style. position:relative fixes the position of the header wrt the map window */
.header{
  position: relative;
}
```

Copy the bootstrap-5 CDN into `index.html`

_index.html_
```html
<head>
 <!---
 boilerplate meta tags
 --->
<link 
href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/css/boot strap.min.css" rel="stylesheet" integrity="sha384-wEmeIV1mKuiNpC+IOBjI7aAzPcEZeedi5yW5f2yOq55WWLwNGmvvx4Um1vskeMj0" crossorigin="anonymous">
<title>React App</title>
</head>
```

Now create a header `<div>` element with the following styles right before the Mapbox instance.

_App.js_
```javascript
return(
<>
 <div className="header">
    <div className="navbar navbar-dark bg-dark">
        <div className="container-fluid">
          <h6 className="mx-auto navbar-brand">
            Global Vaccination Tracker
          </h6>
        </div>
      </div>
 </div>
 <ReactMapGL
    {...viewport}
    mapboxApiAccessToken={process.env.REACT_APP_MAPBOX}
    onViewportChange={nextViewport => setViewport(nextViewport)}     
    mapStyle="<<your_map_style>>"
    >   
 </ReactMapGl>
</>
)

```
You should now see a header component and the new font style should also reflect on the dev server.

### 4. Getting Vaccination figures from the API <a name="section4"></a>

We are going to use two public APIs here for getting country-wise vaccination data. The [first API](https://disease.sh/v3/covid-19/countries) contains general COVID-19 stats along with the ISO code and geo-JSON data for every country. The [second](https://disease.sh/v3/covid-19/vaccine/coverage/countries?lastdays=1&fullData=false) one contains vaccination data.

We are going to send chained fetch requests to both APIs and store the combined response data as an object in a single state variable. 

_App.js_
```javascript

const url_cases = "https://disease.sh/v3/covid-19/countries"
const url_vaccinations = "https://disease.sh/v3/covid-19/vaccine/coverage/countries?lastdays=1&fullData=false"

const [dataCountries, setDataCountries] = useState({})

useEffect(async() => {
    let full_data =  {}

    let res_items = await Promise.all([ fetch(url_cases), fetch(url_vaccinations) ])

    let data_cases = await res_items[0].json()
    data_cases.map((item) => {
      const {country, countryInfo, cases, deaths, population} = item

      full_data[country] = {country, countryInfo, cases, deaths, population}
    })

    let data_vaccinations = await res_items[1].json()
    data_vaccinations.map((item, index) => {
     if(full_data[item.country]){
       full_data[item.country]['total_vaccinations'] = Object.values(item.timeline)[0]
     }
    })

}, [])

``` 

To understand this better, take a look at the response data from both APIs.

_API-1_
```
[
 {
    "updated": 1620970488191,
    "country": "USA",
    "countryInfo": {
      "_id": 840,
      "iso2": "US",
      "iso3": "USA",
      "lat": 38,
      "long": -97,
      "flag": "https://disease.sh/assets/img/flags/us.png"
    },
    "cases": 33626097,
    "todayCases": 0,
    "deaths": 598540,
    "todayDeaths": 0,
    "recovered": 26667199,
    "todayRecovered": 0,
    "active": 6360358,
    "critical": 8611,
    "casesPerOneMillion": 101076,
    "deathsPerOneMillion": 1799,
    "tests": 462795300,
    "testsPerOneMillion": 1391111,
    "population": 332680263,
    "continent": "North America",
    "oneCasePerPeople": 10,
    "oneDeathPerPeople": 556,
    "oneTestPerPeople": 1,
    "undefined": 19119,
    "activePerOneMillion": 19118.53,
    "recoveredPerOneMillion": 80158.64,
    "criticalPerOneMillion": 25.88
  },
 ...
]
```
_API-2_
```
[
 {
    "country": "USA",
    "timeline": {
      "5/13/21": 264680844
 },
...
]
```
So we basically merge the two response objects for each country and store this merged data in a state variable.

Our _dataCountries_ state variable should now contain the country-wise response data object from both APIs.

Here is how the _dataCountries_ variable would look like on the react dev-tools window of your browser.

_dataCountries_
![state variable after data is loaded](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x0cuwwoztzegct165n6p.png)

The screenshot I included contains an additional property called 'size' for every country. This controls the size of the marker depending on the number of doses administered by a country. More on that later!

### 5. Creating markers on the map for every country <a name="section5"></a>

In this step, we are going to use the geographic coordinates of every country to draw markers on the map. You'd need to import the _Marker_ and _Popup_ components from the react-map-gl package. Every Marker takes two properties: the latitude and longitude of a location. We are going to style each marker by giving it a light green background and border radius.  

_App.js_
```javascript
<ReactMapGL
    {...viewport}
    mapboxApiAccessToken={process.env.REACT_APP_MAPBOX}
    onViewportChange={nextViewport => setViewport(nextViewport)}     
    mapStyle="<<your_map_style>>"
    >   
    {dataCountries && Object.values(dataCountries).map((country, index) => {
          return(
            <Marker key={index} latitude={country.countryInfo.lat} longitude={country.countryInfo.long}>
              <div 
              style={{height: 30, width: 30}}
              className="map-marker" 
              > 
              </div>
            </Marker>
          )
        })}
 </ReactMapGl>
```
_index.css_
```css
.map-marker{
  border-radius: 50%;
  cursor: pointer;
  background-color: #1de9b6;
  opacity: 0.5;
}
```
On refreshing the dev server, you should now be able to see a map with green markers on the coordinates of every country.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lem6ore04r1mj2sd6qvl.png)

### 6. Setting variable size markers <a name="section6"></a>

Remember the "size" property that every country in the object had? We are now going to create a method that decides the size of the marker based on the number of vaccine doses a country has administered till now. Let's go with 5 size choices for every marker: 0, 15, 30, 45, 60. Here's how our method will decide the size:
- The countries in the top 25% in terms of doses administered get a marker size of 60.
- Countries lying in the (50-75)% percentile range get a marker size of 45.
- Countries lying in the (25-50)% percentile range get a marker size of 30.
- Countries lying in the bottom 25% get a marker size of 15.
- And finally, countries with no vaccination data or zero doses get a size of 0.

Here's the code for this function. It's named _prepareData()_
```javascript
  const prepareData = (data) => {
    let vaccinations = []
    Object.values(data).map((obj) => {
      if(obj.total_vaccinations){
        vaccinations.push(parseInt(obj.total_vaccinations))
      }
    })
    vaccinations.sort((a,b) => a - b)
    let firstq = vaccinations[Math.floor(vaccinations.length/4)]
    let secondq = vaccinations[Math.floor(vaccinations.length/2)]
    let thirdq = vaccinations[Math.floor(vaccinations.length*3/4)]

    Object.values(data).map((obj) => {
      if(!obj.total_vaccinations){
        obj.size = 0
      }
      else if(obj.total_vaccinations > 0 && obj.total_vaccinations <= firstq){
        obj.size = 15
      }
      else if(obj.total_vaccinations > firstq && obj.total_vaccinations <= secondq){
        obj.size = 30
      }
      else if(obj.total_vaccinations > secondq && obj.total_vaccinations <= thirdq){
        obj.size = 45
      }
      else{
        obj.size = 60
      }
    })

    setDataCountries(data)
  }
```
Now the _useEffect_ hook is modified a bit.

_App.js_
```javascript
  useEffect(async() => {
    let full_data =  {}

    let res_items = await Promise.all([ fetch(url_cases), fetch(url_vaccinations) ])

    let data_cases = await res_items[0].json()
    data_cases.map((item) => {
      const {country, countryInfo, cases, deaths, population} = item

      full_data[country] = {country, countryInfo, cases, deaths, population}
    })

    let data_vaccinations = await res_items[1].json()
    data_vaccinations.map((item, index) => {
     if(full_data[item.country]){
       full_data[item.country]['total_vaccinations'] = Object.values(item.timeline)[0]
     }
    })

    prepareData(full_data)
  }, [])
  
```
Also, we add this size property to the marker by setting it equal to its height and width.

_App.js_
```javascript
<ReactMapGL
    {...viewport}
    mapboxApiAccessToken={process.env.REACT_APP_MAPBOX}
    onViewportChange={nextViewport => setViewport(nextViewport)}     
    mapStyle="<<your_map_style>>"
    >   
    {dataCountries && Object.values(dataCountries).map((country, index) => {
          return(
            <Marker key={index} latitude={country.countryInfo.lat} longitude={country.countryInfo.long}>
              <div 
              style={{height: country.size, width: country.size}}
              className="map-marker" 
              > 
              </div>
            </Marker>
          )
        })}
 </ReactMapGl>
```

You should now be able to see variable size markers on the map. 

### 7. Creating styled tooltips <a name="section7"></a>

Next up, we'll be creating tooltips showing vaccination figures when you hover over or click on a country. Import the _Popup_ component from react-map-gl. Let's start by writing its CSS styles.

_index.css_
```css
/* tooltip card style */
.tooltip-card{
  background-color: white;
  padding: 2px;
  max-width: 250px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: space-around;
}

/* style for every row of content inside the tooltip card  */
.content-row{
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: flex-start;
}

/* tooltip header size  */
.tooltip-header{
  font-weight: 600;
  display: flex;
  font-size: 14px;
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: 0.5rem;
}
```
Next, we create the tooltip component inside the popup element. It is controlled by a new state variable called _tooltipData_. Initially, it's null but when the popup element gets triggered, it is set to the data of the hovered country. 

_App.js_
```javascript
const [tooltipData, setTooltipData] = useState(null)
```

_App.js_
```javascript
 <ReactMapGL
        {...viewport}
        mapboxApiAccessToken={process.env.REACT_APP_MAPBOX}
        onViewportChange={nextViewport => setViewport(nextViewport)}
        mapStyle="mapbox://styles/saket2000/ckolf18ga1lxq17l31rw3lrxk"
      > 
        {dataCountries && Object.values(dataCountries).map((country, index) => {
          return(
            <Marker key={index} latitude={country.countryInfo.lat} longitude={country.countryInfo.long}>
              <div 
              style={{height: country.size, width: country.size}}
              className="map-marker"
              onClick = {() => setTooltipData(country)} 
              > 
              </div>
            </Marker>
          )
        })}
        {tooltipData && <Popup
          latitude={tooltipData.countryInfo.lat}
          longitude={tooltipData.countryInfo.long}
          anchor="bottom"
          closeButton={true}
          onClose={() => setTooltipData(null)}
        >
            <div className="tooltip-card">
              <div className="tooltip-header">
                <img className="tooltip-img" src={tooltipData.countryInfo.flag}></img>
                {tooltipData.country}
              </div>
              <div className="tooltip-content">
                <div className="content-row">
                  <div className="small heading text-secondary me-2">Total doses given</div>
                  <div className="h6 heading">{tooltipData.total_vaccinations.toLocaleString()}</div>
                </div>
                <div className="content-row">
                  <div className="small heading text-secondary me-2">Doses per hundred people</div>
                  <div className="h6 heading">{Math.round((tooltipData.total_vaccinations/tooltipData.population)*100).toLocaleString()}</div>
                </div>
              </div>
            </div>
          </Popup>}
      </ReactMapGL>
```

You should now be able to see a tooltip showing vaccination figures of any country that is hovered over or clicked.

![Complete project checkpoint](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t1hb8dcxcxxyaq3e91pd.png)

So at this point, you have a fully functional interactive map/dashboard that shows vaccination figures of every country. I couldn't add more data points under vaccinations since the APIs didn't host any additional info. It'd be really awesome if someone can find a way to add a _% vaccinated_ section and a first vs second dose breakdown.  

And with this, your app is ready to be launched into the world-wide-web!! Congratulations for making it to the end of this long-drawn boring-ass tutorial. I tried to simplify things as much as possible. So hope you folks enjoyed it. Would really appreciate feedback and suggestions from y'all. If you have any questions or doubts, you can write them down in the comments below. I will try to answer them to the best of my ability

If you want to deploy your React app in the easiest/fastest possible way, do check out [this](https://www.netlify.com/blog/2016/07/22/deploy-react-apps-in-less-than-30-seconds/) blog by Netlify developers. They've outlined the steps in a very easy-to-understand way.