Search code examples
reactjsmapbox-gl

How to use vanilla JS with React component (zoom to bounds on load react mapbox gl)


I am looking to be able to zoom to bounds when my component mounts.

I know this vanilla JS code can achieve the zoom to bounds, but not sure how to massage this into the React code below (I think a need a REF?):

var bounds = new mapboxgl.LngLatBounds();

parkDate.features.forEach(function(feature) { 
    bounds.extend(feature.geometry.coordinates);
});

map.fitBounds(bounds, {
    padding: {top: 20, bottom:20, left: 20, right: 20}
})

Component:

import React, { useState } from "react";
import ReactMapGL, { Marker } from "react-map-gl";
import * as parkDate from "./data.json";
const mapIcon: any = require('../images/mapIcon.png');

export default function App() {

  const [viewport, setViewport] = useState({
    latitude: 45.4211,
    longitude: -75.6903,
    width: "100%",
    height: "400px",
    zoom: 10
  });
  const [selectedPark, setSelectedPark] = useState(null);

  return (
    <div>
      <ReactMapGL
        {...viewport}
        mapboxApiAccessToken="pk.eyJ1IjoiYmVubmtpbmd5IiwiYSI6ImNrY2ozMnJ5dzBrZ28ycnA1b2Vqb2I0bXgifQ.ZOaVtzsDQOrAovh9Orh13Q"
        mapStyle="mapbox://styles/mapbox/streets-v11"
        onViewportChange={viewport => {
          setViewport(viewport);
        }}
      >
        {parkDate.features.map(park => (
          <Marker
            key={park.properties.PARK_ID}
            latitude={park.geometry.coordinates[1]}
            longitude={park.geometry.coordinates[0]}
          >
            <button
              className="marker-btn"
              onClick={e => {
                e.preventDefault();
                setSelectedPark(park);
              }}
            >
              <img src={mapIcon} alt="Map Pointer Icon" />
            </button>
          </Marker>
        ))}
      </ReactMapGL>
      {selectedPark ? (
        <div>
          <h2>{selectedPark.properties.NAME}</h2>
          <p>{selectedPark.properties.DESCRIPTIO}</p>
          <button onClick={e => {
                e.preventDefault();
                setSelectedPark(null);
              }}>X</button>
        </div>
      ) : null}
    </div>
  );
}

Solution

  • Got it working like this:

    import React, { useState } from "react";
    import ReactMapGL, { Marker, WebMercatorViewport } from "react-map-gl";
    import * as parkData from "./data.json";
    const mapIcon: any = require('../images/mapIcon.png');
    
    const applyToArray = (func, array) => func.apply(Math, array)
    
    const getBoundsForPoints = (points) => {
      console.log('Points:', points)
      // Calculate corner values of bounds
      const pointsLong = points.map(point => point.geometry.coordinates[0])
      const pointsLat = points.map(point => point.geometry.coordinates[1])
      const cornersLongLat = [
        [applyToArray(Math.min, pointsLong), applyToArray(Math.min, pointsLat)],
        [applyToArray(Math.max, pointsLong), applyToArray(Math.max, pointsLat)]
      ]
      // Use WebMercatorViewport to get center longitude/latitude and zoom
      const viewport = new WebMercatorViewport({ width: 600, height: 600 })
        // @ts-ignore
        .fitBounds(cornersLongLat, { padding: {top:150, bottom:200, left:100, right:150} }) 
      const { longitude, latitude, zoom } = viewport
      return { longitude, latitude, zoom }
    }
    
    const myMap = () => {
    
      const bounds = getBoundsForPoints(parkData.features);
    
      const [viewport, setViewport] = useState({
        width: "100%",
        height: "600px",
        ...bounds
      });
      const [selectedPark, setSelectedPark] = useState(null);
      
      return (
        <div>
          <ReactMapGL
            {...viewport}
            mapboxApiAccessToken="pk.eyJ1IjoiYmVubmtpbmd5IiwiYSI6ImNrY2ozMnJ5dzBrZ28ycnA1b2Vqb2I0bXgifQ.ZOaVtzsDQOrAovh9Orh13Q"
            mapStyle="mapbox://styles/mapbox/streets-v11"
            onViewportChange={viewport => {
              setViewport(viewport);
            }}
    
          >
            {parkData.features.map(park => (
              <Marker
                key={park.properties.PARK_ID}
                latitude={park.geometry.coordinates[1]}
                longitude={park.geometry.coordinates[0]}
              >
                <button
                  className="marker-btn"
                  onClick={e => {
                    e.preventDefault();
                    setSelectedPark(park);
                  }}
                >
                  <img src={mapIcon} alt="Map Pointer Icon" />
                </button>
              </Marker>
            ))}
          </ReactMapGL>
          {selectedPark ? (
            <div>
              <h2>{selectedPark.properties.NAME}</h2>
              <p>{selectedPark.properties.DESCRIPTIO}</p>
              <button onClick={e => {
                    e.preventDefault();
                    setSelectedPark(null);
                  }}>X</button>
            </div>
          ) : null}
        </div>
      );
    }
    
    export default myMap;