I have a React component where I render an Open Layers 6 map. I pass in lat/long coordinates as a prop (props.userEnteredLocation) to the map which get translated to a format that open layers expects, to be able to render the correct location.
My State is as follows:
const transformCentre = (centre) => {
return transform(centre, "EPSG:4326", "EPSG:3857");
};
const [map, setMap] = useState();
const [centre, setCentre] = useState([74.31071359697442, 31.588167443954312]);
const [featuresLayer, setFeaturesLayer] = useState();
const [point, setPoint] = useState();
const [feature, setFeature] = useState();
const [transformedCentre, setTransformedCentre] = useState(
transformCentre(centre)
);
// create state ref that can be accessed in OpenLayers onclick callback function
// https://stackoverflow.com/a/60643670
const mapRef = useRef();
mapRef.current = map;
I have two React hooks
- The first of which renders the initial map with a red marker and sets the state.
// initialize map on first render - logic formerly put into componentDidMount
useEffect(() => {
console.log("Initialised for the first time");
// Create a point
var point = new Point(transformedCentre);
setPoint(point);
// points/lines/polygons are features that can be used in the vector layer
var feature = new Feature({
geometry: point,
name: "Your address is shown here",
});
var iconStyle = new Style({
image: new Icon({
src: markerImg,
}),
});
feature.setStyle(iconStyle);
setFeature(feature);
// create vector source itself for the marker on the map
var vectorSource = new VectorSource({
features: [feature],
});
// create and add vector source layer
const initalFeaturesLayer = new VectorLayer({
source: vectorSource,
});
// create map
const locationMap = new Map({
target: mapRef.current,
layers: [
new TileLayer({
source: new OSM(),
}),
initalFeaturesLayer,
],
view: new View({
center: transformedCentre,
zoom: 17,
}),
controls: [],
});
setMap(locationMap);
setFeaturesLayer(initalFeaturesLayer);
}, []);
This works, the map is correctly rendered at the right location
- The second hook should re-render the map whenever the prop representing lat/long coordinates changes, I can see in the console that the state does indeed change whenever new coordinates are passed into the props, but the map itself doesn't seem to update
// update map if user changes geo location
useEffect(() => {
console.log("Detected change in User geolocation");
if (props.userEnteredLocation) {
var array = props.userEnteredLocation.split(",");
var newCentre = [parseFloat(array[1]), parseFloat(array[0])];
setCentre(newCentre);
setTransformedCentre(transformCentre(centre));
}
if (map != null) {
console.log("Changing Centre to: " + transformedCentre);
map.getView().setCenter(transformedCentre);
console.log("CENTRE NOW: " + map.getView().getCenter());
point.setCoordinates(transformedCentre);
console.log("POINT CENTRE NOW: " + point.getCoordinates());
feature.setGeometry(point);
console.log("Feature geometry now CENTRE NOW: " + feature.getGeometry());
// set features to map
featuresLayer.setSource(
new VectorSource({
features: [feature],
})
);
featuresLayer.getSource().changed();
}
}, [props.userEnteredLocation]);
//render component
return <div ref={mapRef} className="map-container"></div>;
I've tried map.render() and map.renderSync() as suggested by the FAQ to no avail. I've also tried .changed() and .refresh() on the VectorSource itself, .changed() does nothing and .refresh() removes my red marker completely.
Does anyone know where I'm going wrong here? I'd like for the map to re-render and show the new location coming from the props and show the red map marker in that location.
I'm tempted to just create a new map, i.e. paste everything I do on initial render into the hook that changes on props change, but that seems sub optimal.