Mapbox and Tool Tips in React.js
Tags: react, beginners, javascript, codenewbie
Published: 2020-04-10 20:29:39

While perusing popular posts, I was inspired by this [COVID-19 map](https://dev.to/alemesa/how-to-create-a-covid-19-map-with-mapbox-and-react-3jgf) to get into learning Mapbox. The [project](https://github.com/alemesa/mapbox-covid19) covers a lot of what I do here and I hope I'm not coming off trying to steal anyone's thunder. This isn't a post about my creativity. I am a beginner/bootcamp student and felt like I could even further simplify the process of just using Mapbox at all, let alone connecting it to interesting COVID data and formatting.

##Basic Setup of Mapbox 

[Mapbox](https://docs.mapbox.com/mapbox-gl-js/api/) GL JS is a JavaScript library that uses WebGL to render interactive maps from vector tiles and Mapbox styles. This [tutorial](https://docs.mapbox.com/help/tutorials/use-mapbox-gl-js-with-react/) on basic setup in React is very good and helpful! This post will mostly walk through/combine several already very good tutorials. Once again, not trying to reinvent the wheel here, but hoping to combine some good existing wheels.

![Alt text](https://media.giphy.com/media/XDdfmJosb0ojKrrG4g/giphy.gif)<figcaption>End goal!</figcaption>

Basic React setup:
```
npx create-react-app your-app-name
cd your-app-name
npm install mapbox-gl
```

or add `mapbox-gl` to `package.json` manually and then run `npm install`. Both seem to accomplish the same thing - creating `package-lock.json` and having a `package.json` that contains `mapbox-gl` in `dependencies`.

Now this is probably a trivial difference, but the Mapbox tutorial includes everything in `index.js`, I've been learning React with keeping `index.js` short - like this:

```javascript
// src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

ReactDOM.render(<App />, document.getElementById("root"));
```

And then keeping most of my code in `App.js` for now.

```javascript
// src/App.js

import React, { Component } from 'react'
import "./App.css";
import mapboxgl from 'mapbox-gl';

mapboxgl.accessToken = process.env.REACT_APP_MAPBOX_ACCESS_TOKEN;

export class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      lng: -90,
      lat: 45,
      zoom: 3
    };}

  componentDidMount() {
    const map = new mapboxgl.Map({
      container: this.mapContainer,
      style: 'mapbox://styles/mapbox/streets-v11',
      center: [this.state.lng, this.state.lat],
      zoom: this.state.zoom
    });}

  render() {
    return (
      <div className="App">
        <div ref={element => this.mapContainer = element} className="mapContainer" />
      </div>
    )}}

export default App
```

and now we have a basic Mapbox! For the access token, you simply sign up for a free and easy account on Mapbox, and then, small side note that isn't super important since it's unlikely anyone would want to steal your free token, but good practice to use `.env` and `.gitignore`:

```
// in project main directory
touch .env

// .env
REACT_APP_MAPBOX_ACCESS_TOKEN=<mytoken>

// App.js
mapboxgl.accessToken = process.env.REACT_APP_MAPBOX_ACCESS_TOKEN;

// .gitignore
.env
```

Fun note of caution! ⚠️ If you get the error `Invalid LngLat latitude value: must be between -90 and 90` - you probably have your longitude and latitude mixed up! If you only knew how many things I tried to fix this without just simply googling the error because I didn't think I could be making such a simple mix up...

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/qcxkmt9qikf2z4ayg1fo.jpg)<figcaption>Longitude boi</figcaption>

Anyway, at this point I have my coords set to SF. You can mess around with console.logs and the React dev tools for state to experiment with different starting coords and zoom.

```javascript
    this.state = {
      lat: 37.7524,
      lng: -122.4343,
      zoom: 11.43
    };
  }
```

Still following the Mapbox tutorial - here is how you add a bar that shows your coordinates and zoom as you move around the map.

```javascript
// added to existing componentDidMount() function 

componentDidMount() {
...
    map.on('move', () => {
      this.setState({
      lng: map.getCenter().lng.toFixed(4),
      lat: map.getCenter().lat.toFixed(4),
      zoom: map.getZoom().toFixed(2)
      });
      });
    }
```

and in `render()`, add the follow `<div>` just under `<div className="App">`:

```html
// added to existing render() 
...
   <div className="App">
     <div className="sidebarStyle">
        Longitude: {this.state.lng} | Latitude: {this.state.lat} | Zoom: {this.state.zoom}
     </div>
```

At this point you should also have something like this in `src/App.css`. Note if something isn't working but you aren't getting any errors, it might be a CSS issue - a lot of this involves styling from Mapbox.

```css
.mapContainer {
 position: absolute;
 top: 0;
 right: 0;
 left: 0;
 bottom: 0;
}
 
.sidebarStyle {
 display: inline-block;
 position: absolute;
 top: 0;
 left: 0;
 margin: 12px;
 background-color: #404040;
 color: #ffffff;
 z-index: 1 !important;
 padding: 6px;
 font-weight: bold;
 }
```

A small tangent I found interesting but easy to look up - if you want to change the icon that appears in the browser tab next to title, save an image to your public folder, and add to `index.html` where the default icon link is already set:

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/map.png" />
```

and just change the portion after `%PUBLIC_URL%/`. I had saved mine as 'map.png' as you can see here.

This is where the Mapbox tutorial pretty much ends, and then links to examples on how to expand. As with everything in programming, there are so many good options! And different ways of doing every one of those options. For some reason, tool tips stood out to me. I didn't even know 'tool tips' was the official term for these little hover popups until now.

I had also come across this great blog [post](https://dev.to/lindelof/6-very-useful-react-component-libraries-g41) on React component libraries, and was interested in using `react-portal-tooltip`. But, I found the Mapbox official example on tooltips a bit easier to follow directly after this setup. `react-portal-tooltip` is more general, and useful for all sorts of apps, which is great, but it helped me to start with the Mapbox specific example to learn what was going on here.

##Tooltips

The [tooltip](https://en.wikipedia.org/wiki/Tooltip) (or `infotip`, or  `hint`) is a common graphical user interface element — a small "hover box" with information about the item. Again, pretty basic stuff, but I am a coding bootcamp student, and we just finished vanilla JS/started React, so this seemed like a cool thing that would have been harder without React! I always like to think of a clear example in my mind of why I am learning something, instead of just accepting it because it's a buzzword. Anyway!

This is the [repo](https://github.com/mapbox/mapbox-react-examples/tree/master/react-tooltip) for the Mapbox specific tooltip example that I'm starting with.

First, create a `components` directory within `src` and a `ToolTipBox.js` `Component` (or you could name it anything you want, something shorter like just `ToolTip.js`, but if I end up using a tooltip library later, that could be not specific enough). Import the component, as well as `ReactDOM` which we now need in `App.js`, and add the following code:

```javascript
...
import ReactDOM from 'react-dom';
import ToolTipBox from './components/ToolTipBox'
...

export class App extends Component {
 mapRef = React.createRef();
 tooltipContainer;
 
 componentDidMount() {
   // Container to put generated content in
   this.tooltipContainer = document.createElement('div');
 
   const map = new mapboxgl.Map({
     container: this.mapRef.current,
 ...
   });
 ...
   const tooltip = new mapboxgl.Marker(this.tooltipContainer).setLngLat([0,0]).addTo(map);
 
   map.on('mousemove', (e) => {
     const features = map.queryRenderedFeatures(e.point);
     tooltip.setLngLat(e.lngLat);
     map.getCanvas().style.cursor = features.length ? 'pointer' : '';
     this.setTooltip(features);
   }
   );
 }

 render() {
   return (
     <div className="App">
      ...
       <div ref={this.mapRef} className="absolute top right left bottom"/>
     </div>)}}
...
```

Notice in `map.on('mousemove')` I have `this.setTooltip(features)`. I define this outside of `componentDidMount()` and it connects to my `ToolTipBox` component.

```javascript 
export class App extends Component {
...
 setTooltip(features) {
   if (features.length) {
     ReactDOM.render(
       React.createElement(
         ToolTipBox, {
           features
         }
       ),
       this.tooltipContainer
     );
   } else {
     ReactDOM.unmountComponentAtNode(this.tooltipContainer);
   }
 }
...
}
```
Important things used here - [`React.createRef()`](https://reactjs.org/docs/react-api.html#reactcreateref), which is good for:

>Managing focus, text selection, or media playback.
>Triggering imperative animations.
>Integrating with third-party DOM libraries.

But should be avoided for anything that can be done declaratively.

[`queryRenderedFeatures`](https://docs.mapbox.com/mapbox-gl-js/api/#map#queryrenderedfeatures) comes from the Mapbox API and is how we get the 'features' that will gives us the tooltips/popups info!
 
[`React.createElement()`](https://reactjs.org/docs/react-api.html#createelement) - this doesn't seem common/standard and would usually be done with JSX. The React docs recommend using JSX and not `React.createElement()`, but it seems fine here.

Now more on the `ToolTipBox` component, which uses [`Static PropTypes`](https://reactjs.org/docs/typechecking-with-proptypes.html) to validate that the 'features' returned from `queryRenderedFeatures` is an array.

```javascript
// src/components/ToolTipBox.js

import React from 'react'
import PropTypes from 'prop-types'
 
export default class Tooltip extends React.Component {
 
 static propTypes = {
   features: PropTypes.array.isRequired
 };
 
 render() {
   const { features } = this.props;
 
   const renderFeature = (feature, i) => {
     return (
       <div key={i}>
         <strong className='mr3'>{feature.layer['source-layer']}:</strong>
         <span className='color-gray-light'>{feature.layer.id}</span>
       </div>
     )
   };
 
    return (
      <div className="flex-parent-inline absolute bottom">
        <div className="flex-child">
          {features.map(renderFeature)}
        </div>
      </div>
    );}}
```

There's a lot going on with CSS here, and you'll notice the actual example I am copying from had more styling, but I removed it and added some to my own `App.css` for simplicity of code blocks here. Here's what I added to my CSS after this step:

```css
.flex-parent {
  flex-direction: column;
  position: absolute;
}
.flex-child {
  color: white;
  background: gray;
  text-overflow: clip;
  padding: 1rem;
}
```

Pretty simple, just enough styling to see a basic box show up. Not that aesthetic, but I can come back to that later, and so you can you!

Either way, though, unless you want to completely define all your own CSS, which I did not, you should probably have your `index.html` looking like the example as well, as they import stylesheets here from mapbox:

```html
<!DOCTYPE html>
<html lang="en">
 <head>
   <meta charset="utf-8" />
   <link rel="icon" href="%PUBLIC_URL%/map.png" />
   <meta name="viewport" content="width=device-width, initial-scale=1" />
   <meta name="theme-color" content="#000000" />
   <meta
     name="description"
     content="Web site created using create-react-app"
   />
   <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
   <link href='https://api.mapbox.com/mapbox-assembly/mbx/v0.18.0/assembly.min.css' rel='stylesheet'>
   <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.39.1/mapbox-gl.css' rel='stylesheet' />
   <title>MapBox React Example</title>
 </head>
   <div id="root"></div>
   <script src='https://api.mapbox.com/mapbox-assembly/mbx/v0.18.0/assembly.js'></script>
 </body>
</html>
```

##React Tooltip Library
This post is already a little long so I won't actually go into `react-portal-tooltip`. But one very annoying thing I overcame while exploring it and thought worth sharing - if you get this guy:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/b91a2kk12uwsvwkvqqx7.png)
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ti4f1okjrx9xsgp57i0d.png)
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/dwp9z6r5yc607mlzua6l.png)
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/f1iwtmzenk5bp6k7vn9l.png)

There are many solutions on StackOverflow. This one worked for me:

```
touch src/declare_modules.d.ts

// in declare_modules.d.ts
declare module "react-portal-tooltip";

// if it still doesn't work, add import in `App.js`
// App.js
...
import './declare_modules.d.ts'
```

Thanks for reading!

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/2puv8k6ydutphs4zcesj.png)

##Resources:
* [Mapbox API](https://docs.mapbox.com/mapbox-gl-js/api/)
* [Mapbox React Tutorial](https://docs.mapbox.com/help/tutorials/use-mapbox-gl-js-with-react/)
* [Mapbox React Examples](https://github.com/mapbox/mapbox-react-examples)
* [COVID-19 Mapbox](https://github.com/alemesa/mapbox-covid19)
* [React Tooltip Component Library](https://github.com/romainberger/react-portal-tooltip)
* [My repo for this example](https://github.com/sylviapap/map-example-react) :)