nizz0k
nizz0k

Reputation: 521

How to refactor React Leaflet GeoJSON component to use the useMap function with Next.js?

I'm working on a React-Leaflet application using Next.js and I'm struggling to refactor my GeoJSON component to be able to implement a 'zoom to feature' event using the useMap function. The React-Leaflet documentation states that this must be used in a descendant of the MapContainer per this other SO post, however my attempt to follow the example in the post results in no layer being added to the map but without any errors. I think this might be related to how the data props are passsed to the Map component itself, but being new to React/Next, I'm not sure where the error is, or what a better way to approach this is.

My current implementation has the Next.js Map page load static GeoJSON from a file via GetServerSideProps, which passes the props with points to the Map component itself. Where I'm struggling is how to refactor the Map component and the React-Leaflet GeoJSON component.

import { useRef } from 'react';
import { MapContainer, TileLayer, GeoJSON, useMap } from 'react-leaflet'
import 'leaflet/dist/leaflet.css'

const Map = ({points}) => {

const markerOptions = {radius: 2, weight: 1, opacity: 1, fillOpacity: 0.8, }

const markerStyles = function(feature) {
  switch (feature.properties.type) {
      case 'Sticker': return {color: '#a50026'};
      case 'Mural':   return {color: '#d73027'};
      case 'Marker':   return {color: '#f46d43'};
      case 'Characters':   return {color: '#fdae61'};
      case 'Letters':   return {color: '#fee090' };
      case 'Tippex':   return {color: '#ffffbf'};
      case 'Spray':    return {color: '#e0f3f8'};
      case 'Chalk':    return{color: '#abd9e9'};
      case 'Label maker sticker':    return{color: '#74add1' };
      case 'Poster':    return{color: '#4575b4'};
      }
}
// Map Events
const geoJsonRef = useRef();
const onMouseOut = (e) => {
  var layer = e.target;
  geoJsonRef.current.setStyle(markerOptions);
}

const onMouseOver = (e) => {
  var layer = e.target;

    layer.setStyle({
        weight: 5,
        color: '#666',
        dashArray: '',
        fillOpacity: 0.7,
        radius: 3
    });

    if (!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) {
        layer.bringToFront();
    }
}

function onEachFeature(feature, layer){
  if(feature.properties){
    layer.bindPopup("<div class='popupImage'</div><img src=" + "https://d2qr25zh4rluwu.cloudfront.net/" + encodeURI(feature.properties.filename) + ".jpg " + "alt='peng spot photo'" + "height='200px'"  + " " + ">" + "<div>" + "Type: " + feature.properties.type + "</div><div>" + "Description: " + feature.properties.desc + " </div>")
  }
  layer.on({
      mouseover: onMouseOver,
      mouseout: onMouseOut,
  });
}

function pointToLayer(feature, latLng){
  return L.circleMarker(latLng, markerOptions)
}

return (
    <>
    <MapContainer center={[50.1109, 8.6821]} zoom={14} scrollWheelZoom={false} style={{height: "100%", width: "100%"}} renderer={L.canvas()}>
      <TileLayer
    url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
    attribution="&copy; <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors"
  />
  
    <GeoJSON data={points} pointToLayer={pointToLayer} pathOptions={markerStyles} onEachFeature={onEachFeature}  ref={geoJsonRef}  />
    </MapContainer>
</>
)
}

export default Map

Per the mentioned SO post, I've tried to refactor the GeoJSON component code into a separate fuctional component to be added to the map container but this doesn't seem to work as the layer isn't added to the map. As this happens with no errors, I'm not sure what the issue is. Here is my first attempt at the refactor:

import { useRef } from 'react';
import { MapContainer, TileLayer, GeoJSON, useMap } from 'react-leaflet'
import 'leaflet/dist/leaflet.css'

const Map = () => {

return (
    <>
    <MapContainer center={[50.1109, 8.6821]} zoom={14} scrollWheelZoom={false} style={{height: "100%", width: "100%"}} renderer={L.canvas()}>
      <TileLayer
    url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
    attribution="&copy; <a href=&quot;http://osm.org/copyright&quot;>OpenStreetMap</a> contributors"
  />
  
   <MapContent />
    </MapContainer>
</>
)
}

const MapContent = ({points}) =>{
  const map = useMap();
  const zoomToFeature = (e) => {  
  const latLngs = [e.target.getLatLng()];
  const markerBounds = L.latLngBounds(latLngs);
  map.fitBounds(markerBounds);
  }
  
  const markerOptions = {radius: 2, weight: 1, opacity: 1, fillOpacity: 0.8, }
  
  const markerStyles = function(feature) {
    switch (feature.properties.type) {
        case 'Sticker': return {color: '#a50026'};
        case 'Mural':   return {color: '#d73027'};
        case 'Marker':   return {color: '#f46d43'};
        case 'Characters':   return {color: '#fdae61'};
        case 'Letters':   return {color: '#fee090' };
        case 'Tippex':   return {color: '#ffffbf'};
        case 'Spray':    return {color: '#e0f3f8'};
        case 'Chalk':    return{color: '#abd9e9'};
        case 'Label maker sticker':    return{color: '#74add1' };
        case 'Poster':    return{color: '#4575b4'};
        }
  }
  // Map Events
  const geoJsonRef = useRef();
  const onMouseOut = (e) => {
    var layer = e.target;
    geoJsonRef.current.setStyle(markerOptions);
  }
  
  const onMouseOver = (e) => {
    var layer = e.target;
  
      layer.setStyle({
          weight: 5,
          color: '#666',
          dashArray: '',
          fillOpacity: 0.7,
          radius: 3
      });
  
      if (!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) {
          layer.bringToFront();
      }
  }
  
  function onEachFeature(feature, layer){
    if(feature.properties){
      layer.bindPopup("<div class='popupImage'</div><img src=" + "https://d2qr25zh4rluwu.cloudfront.net/" + encodeURI(feature.properties.filename) + ".jpg " + "alt='peng spot photo'" + "height='200px'"  + " " + ">" + "<div>" + "Type: " + feature.properties.type + "</div><div>" + "Description: " + feature.properties.desc + " </div>")
    }
    layer.on({
        mouseover: onMouseOver,
        mouseout: onMouseOut,
      click: zoomToFeature
    });
  }
  
  function pointToLayer(feature, latLng){
    return L.circleMarker(latLng, markerOptions)
  }  
  return(
    <GeoJSON data={points} pointToLayer={pointToLayer} pathOptions={markerStyles} onEachFeature={onEachFeature}  ref={geoJsonRef}  />
  )

}
export default Map

As above, I don't know why this isn't working but I expect it's with how I'm trying to pass the data props to the GeoJSON layer. Being relatively new to Next/React I'm still fuzzy on accessing and passing the data around.

Upvotes: 1

Views: 1390

Answers (1)

nizz0k
nizz0k

Reputation: 521

So, what worked was pulling the GeoJSON into a separate component with a data prop like so:

import { useRef } from 'react'
import { GeoJSON, useMap } from 'react-leaflet'; 

const GeoJSONLayer = ({ data }) =>{
    const map = useMap();
    const zoomToFeature = (e) => {  
    const latLngs = [e.target.getLatLng()];
    const markerBounds = L.latLngBounds(latLngs);
    map.fitBounds(markerBounds);
    }
    
    const markerOptions = {radius: 2, weight: 1, opacity: 1, fillOpacity: 0.8, }
    
    const markerStyles = function(feature) {
      switch (feature.properties.type) {
          case 'Sticker': return {color: '#a50026'};
          case 'Mural':   return {color: '#d73027'};
          case 'Marker':   return {color: '#f46d43'};
          case 'Characters':   return {color: '#fdae61'};
          case 'Letters':   return {color: '#fee090' };
          case 'Tippex':   return {color: '#ffffbf'};
          case 'Spray':    return {color: '#e0f3f8'};
          case 'Chalk':    return{color: '#abd9e9'};
          case 'Label maker sticker':    return{color: '#74add1' };
          case 'Poster':    return{color: '#4575b4'};
          }
    }
    // Map Events
    const geoJsonRef = useRef();
    const onMouseOut = (e) => {
      var layer = e.target;
      geoJsonRef.current.setStyle(markerOptions);
    }
    
    const onMouseOver = (e) => {
      var layer = e.target;
    
        layer.setStyle({
            weight: 5,
            color: '#666',
            dashArray: '',
            fillOpacity: 0.7,
            radius: 3
        });
    
        if (!L.Browser.ie && !L.Browser.opera && !L.Browser.edge) {
            layer.bringToFront();
        }
    }
    
    function onEachFeature(feature, layer){
      if(feature.properties){
        layer.bindPopup("<div class='popupImage'</div><img src=" + "https://d2qr25zh4rluwu.cloudfront.net/" + encodeURI(feature.properties.filename) + ".jpg " + "alt='peng spot photo'" + "height='200px'"  + " " + ">" + "<div>" + "Type: " + feature.properties.type + "</div><div>" + "Description: " + feature.properties.desc + " </div>")
      }
      layer.on({
          mouseover: onMouseOver,
          mouseout: onMouseOut,
        click: zoomToFeature
      });
    }
    
    function pointToLayer(feature, latLng){
      return L.circleMarker(latLng, markerOptions)
    }  
    return(
      <GeoJSON data={data} pointToLayer={pointToLayer} pathOptions={markerStyles} onEachFeature={onEachFeature}  ref={geoJsonRef}  />
    )
  
  }

  export default GeoJSONLayer

And then using the component like so in my map component:

   <GeoJSONLayer data={points}/>

Upvotes: 0

Related Questions