React-Leaflet – Changing Color of GeoJSON Layer After State Change

featuresgeojsonleafletreactstyle

I have an issue, I have a react-leaflet map with GeoJSON layers. Also, I have added some state components like cascader with some name of countries options, like Austria, Belgium, etc… I want to have it like this. After the state from cascader is done (so after I click on a country in cascader), I want to change the color of the selected country from that cascader and display it on the map. For example, if I choose Austria, I want to have colored Austria on the map. Also cascader is from the Ant Desing library. And selectedCountries is another state that is not important.

const [currentValueCascader, setCurrentValueCascader] = React.useState([])

function onEachCountry(country, layer) {
    const countryName = country.properties.ADMIN

      if (currentValueCascader.includes(countryName)) {
        layer.setStyle({ fillColor: 'yellow', weight: 2, fillOpacity: 5 })
      } else {
        layer.setStyle({ color: "#000000", fillColor: "white", weight: 1 })
      }
}

return(
<Cascader
                showSearch
                style={{ width: 256 }}
                options={countries}
                onChange={(e) => {
                  setCurrentValueCascader(e)
                }}
                placeholder="Search country"
                value={currentValueCascader} 
                multiple
    
    
              /> 
<MapContainer
            style={{ height: "80vh", width: "auto" }}
            zoom={2.3}
            scrollWheelZoom={true}
            minZoom={2.3}
            center={[30, 0]}>
            <TileLayer url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' />
            <GeoJSON data={features} onEachFeature={(a, b) => { onEachCountry(a, b, selectedCountries) }} style={(a, b) => { onEachCountry(a, b, selectedCountries) }}}  />
          </MapContainer>
    )

I think that somehow the Leaflet map is not re-rendering that chosen option or maybe that GeoJSON, but don't know how to solve it.

Best Answer

There are several ways how to deal with this. Since I'm not familiar with React, answer will be for plain HTML/JS. Country name is being input through input HTML tag:

<label for="country">Country:</label>
<input type="text" id="country" name="country">

First possibility is to leave setting the style as you have done it. If the style is set with onEachFeature option function, then style is set only once, at the time of GeoJSON data load. To change style at the time country name input, all features/countries have to be iterated and style set again according to selected country name. Group layer method .eachLayer can be used for this.

Plaing HTML/JS code could then look something like this:

var inputElement = document.getElementById('country');

function onEachCountry(feature, layer) {
  const countryName = feature.properties.name;

    if (inputElement.value.includes(countryName)) {
      layer.setStyle({ fillColor: 'yellow', weight: 2, fillOpacity: 5 })
    } else {
      layer.setStyle({ color: "#000000", fillColor: "white", weight: 1 })
    }
}
$.getJSON( "data/countries.json")
 .done(function(data) {
   var vectorLayer = L.geoJson(data, {
     onEachFeature: onEachCountry
   });
   map.addLayer(vectorLayer);
   inputElement.addEventListener('input', function(evt) {
     vectorLayer.eachLayer(function(layer) {
       onEachCountry(layer.feature, layer);
     });
   });
 });

Second possibility is to use GeoJSON layer style option to set layer style and then GeoJSON layer .setStyle method to set style again when country name is selected.

In this case code could look something like this:

var inputElement = document.getElementById('country');

function countryStyle(feature) {
  var style;
  var countryName = feature.properties.name;
  if (inputElement.value.includes(countryName)) {
    style = { fillColor: 'yellow', weight: 2, fillOpacity: 5 };
  } else {
    style = { color: "#000000", fillColor: "white", weight: 1 };
  }
  return(style);
}

$.getJSON( "data/countries.json")
 .done(function(data) {
   var vectorLayer = L.geoJson(data, {
     style: countryStyle
   });
   map.addLayer(vectorLayer);
   inputElement.addEventListener('input', function(evt) {
     vectorLayer.setStyle(countryStyle);
   });
 });

To avoid iterating through all the layers/countries each time country name is changed, third possibility would be to build translating table/object between country name and corresponding Leaflet layer id, retrieved by .getLayerId method. This way country layer for desired country can be directly accesses with the help of .getLayer method.

In this case code could look something like this:

var inputElement = document.getElementById('country');

function countryStyle(feature) {
  return({ color: "#000000", fillColor: "white", weight: 1 });
}

var selectedCountry = null;
var countryToLayerId = {};

$.getJSON( "data/countries.json")
 .done(function(data) {
   var vectorLayer = L.geoJson(data, {
     style: countryStyle
   });
   map.addLayer(vectorLayer);
   vectorLayer.eachLayer(function(layer) {
     countryToLayerId[layer.feature.properties.name] = vectorLayer.getLayerId(layer);
   });
   inputElement.addEventListener('input', function(evt) {
     if (selectedCountry) {
       vectorLayer.resetStyle(selectedCountry);
       selectedCountry = null;
     }
     if (countryToLayerId[inputElement.value]) {
       selectedCountry = vectorLayer.getLayer(countryToLayerId[inputElement.value]);
       selectedCountry.setStyle({ fillColor: 'yellow', weight: 2, fillOpacity: 5 });
     }
   });
 });
Related Question