BennKingy
BennKingy

Reputation: 1593

How to make mapbox resize when changing the browser width

How can I adjust the code below so that map resizes when I resize the browser/screen? (First time using Mapbox without a senior developers help).

From my own research, there are code libraries that can add an event listener on the map container and listen for that to resize, then I can then call map.resize() programmatically. However, I would like to know what best practice are as that way feels a bit hacky.

Code

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:20, bottom: 90, left:20, right:20} }) 
  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.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 className={ styles.officeInfo }>
          <div className={ styles.officeInfoImage }>
            <img src={selectedPark.properties.IMAGE} />
          </div>
          <div className={ styles.officeInfoContent }>
            <h3>{selectedPark.properties.NAME}</h3>
            <p>{selectedPark.properties.DESCRIPTION}</p>
            <button onClick={e => {
              e.preventDefault();
              setSelectedPark(null);
            }}>X</button>
          </div>
        </div>
      ) : null}
    </div>
  );
}

Code demo https://codesandbox.io/s/immutable-glitter-hokq6?file=/src/App.js

I will legit buy you a coffee or two for helping <3

Upvotes: 5

Views: 4937

Answers (1)

mausworks
mausworks

Reputation: 1625

Having worked with mapbox pretty extensively, I can tell you that map.resize() will have to be called at some point, either by you, or by the react-map-gl bindings internally.

So, you need to first create a resize-handler, I prefer to do this in a hook:

const useResize = (handler) => {
  useEffect(() => {
    window.addEventListener("resize", handler);

    return () => {
      window.removeEventListener("resize", handler);
    };
  }, [handler]);
};

Then you can use this in your component as such:

const onResize = useCallback(() => {
  setViewport({ ...viewport });
}, []);

useResize(onResize);

Since you update the viewport object, react-map-gl will recognize this as a state-change and update the viewport accordingly, probably by calling map.resize() internally.

Here is a sandbox: https://codesandbox.io/s/upbeat-tdd-o8vn3?file=/src/App.js

The other (arguably better) solution is to simply call map.resize() yourself. To do this, simply capture the map object on load:

<ReactMapGL
 {...viewport}
 onLoad={({ map }) => setMap(map)}
>

Then call map.resize() in your resize-handler:

const onResize = useCallback(() => {
  if (map) {
    map.resize();
  }
}, [map]);

This solution is going to have better performance, since it is closer to the metal and doesn't require cloning the current viewport object.

Here's a sandbox for this solution: https://codesandbox.io/s/optimistic-kalam-8p8jo?file=/src/App.js

Upvotes: 4

Related Questions