Custom Component using Mapbox + React

Hi Retool,

Hoping y’all could help steer us in the right direction.

Our goal is to manually draw polygons on a map and send their JSON representations via API.

To this end, we would like to create a custom component with a Mapbox object (with polygon-drawing capabilities) and extract the generated JSON.

We can successfully implement a Mapbox component that draws polygons (see: https://docs.mapbox.com/mapbox-gl-js/example/mapbox-gl-draw/). However, it’s created with plain JS. Our understanding is that one needs a custom component that uses react to enable data sharing across the Retool dashboard.

When creating a custom component with React, we are having trouble creating the Mapbox component. We suspect an issue with imports or with the way the component is declared. The generated error messages don’t provide sufficient information on possible error sources. Would you, by chance, have suggestions on how to proceed?

Thank you.

<style>
  body {
    margin: 0;
  }
</style>
<script src="https://cdn.tryretool.com/js/react.production.min.js" crossorigin></script>
<script src="https://cdn.tryretool.com/js/react-dom.production.min.js" crossorigin></script>
<script src="https://unpkg.com/@material-ui/core@3.9.3/umd/material-ui.production.min.js"></script>

<script src="https://api.mapbox.com/mapbox-gl-js/v0.39.1/mapbox-gl.js"></script>

<script type="text/babel">
	const mapboxgl = window['mapbox-gl'];

  const MyCustomComponent = ({ triggerQuery, model, modelUpdate }) => (
    <p>Hello, Retool!</p>
  );


	const map = new mapboxgl.Map({
        container: 'map', // container id
        style: 'mapbox://styles/mapbox/dark-v10', //hosted style id
        center: [-99.874, 19.76], // starting position
        zoom: 8 // starting zoom
    });

  const ConnectedComponent = Retool.connectReactComponent(MyCustomComponent);
  ReactDOM.render(<ConnectedComponent />, document.getElementById('react'));
</script>

<div id="react"></div>

Hi @mindatasleep! Welcome to the community! :sunglasses:

It actually does look like we have a demo of this, which can be found here!

And here’s a sample of the custom component code to get you started:

<html>
<head>
<meta charset='utf-8' />
<title>Show drawn polygon area</title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.1/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.1/mapbox-gl.css' rel='stylesheet' />
<style>
body { margin:0; padding:0; }
#map { position:absolute; top:0; bottom:0; width:100%; }
</style>
</head>
<body>
 
<style>
.calculation-box {
height: 75px;
width: 150px;
position: absolute;
bottom: 40px;
left: 10px;
background-color: rgba(255, 255, 255, .9);
padding: 15px;
text-align: center;
}
 
p {
font-family: 'Open Sans';
margin: 0;
font-size: 13px;
}
</style>
 
<script src='https://api.tiles.mapbox.com/mapbox.js/plugins/turf/v3.0.11/turf.min.js'></script>
<script src='https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-draw/v1.0.9/mapbox-gl-draw.js'></script>
<link rel='stylesheet' href='https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-draw/v1.0.9/mapbox-gl-draw.css' type='text/css'/>
<div id='map'></div>
<div class='calculation-box'>
<p>Draw a polygon using the draw tools.</p>
<div id='calculated-area'></div>
</div>
 
<script>
mapboxgl.accessToken = 'xxxxxxx';
/* eslint-disable */
var map = new mapboxgl.Map({
container: 'map', // container id
style: 'mapbox://styles/mapbox/satellite-v9', //hosted style id
center: [-91.874, 42.760], // starting position
zoom: 12 // starting zoom
});
 
var draw = new MapboxDraw({
displayControlsDefault: false,
controls: {
polygon: true,
trash: true
}
});
map.addControl(draw);
 
map.on('draw.create', updateArea);
map.on('draw.delete', updateArea);
map.on('draw.update', updateArea);
 
function updateArea(e) {
var data = draw.getAll();
window.Retool.modelUpdate({ data })


var answer = document.getElementById('calculated-area');
if (data.features.length > 0) {
var area = turf.area(data);
// restrict to area to 2 decimal points
var rounded_area = Math.round(area*100)/100;
answer.innerHTML = '<p><strong>' + rounded_area + '</strong></p><p>square meters</p>';
} else {
answer.innerHTML = '';
if (e.type !== 'draw.delete') alert("Use the draw tools to draw a polygon!");
}
}
 
</script>
 
</body>
</html>
2 Likes

Hi @ben,

Thanks for sharing this sample!

The example shows how to retrieve data from a custom MapBox component, but how do I pass data to my MapBox component?

I assumed I could just read window.Retool.model after passing in data through the Model component field, but the below code does not work.

Model

{
    earthquakeUrl: "https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson",
}

iFrame Code

<!DOCTYPE html>
<html>
  <head>
    <meta charset='utf-8' />
    <title>View deliveries</title>

      <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
    <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.1/mapbox-gl.js'></script>
    <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.1/mapbox-gl.css' rel='stylesheet' />
    <style>
      body { margin:0; padding:0; }
      #map { position:absolute; top:0; bottom:0; width:100%; }
    </style>
  </head>

<body>
 
<div id='map'></div>
 
<script>
mapboxgl.accessToken = 'omitted';
var map = new mapboxgl.Map({
  container: 'map',
  style: 'mapbox://styles/mapbox/light-v10',
  center: [60.0, 50.9],
  zoom: 9.5
});
 
map.on('load', () => {
  map.addSource("deliveries", {
    type: "geojson",
    data: window.Retool.model.earthquakeUrl,
  });
 
  map.addLayer({
    id: "unclustered-point",
    type: "circle",
    source: "deliveries",
    paint: {
      "circle-color": "#11b4da",
      "circle-radius": 4,
      "circle-stroke-width": 1,
      "circle-stroke-color": "#fff"
    }
  });
</script>
 
</body>
</html>

Hardcoding the URL in the iFrame code does work, however, so the problem lies in how I’m passing data to the component.

EDIT

Upon further investigation, the window.Retool object has triggerQuery, modelUpdate, and updateModel properties, but no model property. Does that mean we have to use a React component? If so, would be great to see an example!

1 Like

Would love to see an example aswell :slight_smile:

Hey there!

In order to access the model property, we do need to subscribe to the model and any changes incoming to the model using the following:

window.Retool.subscribe(function(model) {
  if(!model){ return }
  let points = model.points
  <REST OF CODE HERE :)>
}

The subscription would need to wrap any code that relies on model changes, so keep that in mind as well!

EDIT:

The documentation for custom components + vanilla js is located at the bottom of this docs page

2 Likes

Ah I see, I just needed to scroll down a little more :no_mouth:

Thanks!