Reputation: 363
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
// 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
// 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.
Upvotes: 1
Views: 4083
Reputation: 363
For anyone whom this might help, I realised the issue was actually just that useEffect() wasn't using the updated state until the next render of the component. In order for it to use the latest co-ordinates, I assigned the required centre of the map to a var before passing it to the Openlayers view and layout.
Here's the code that works:
function BusinessLocationMap(props) {
// Transform the centre into something openlayers understands
const transformCentre = (centre) => {
if (centre != null) {
return transform(centre, "EPSG:4326", "EPSG:3857");
}
};
const [map, setMap] = useState();
const [centre] = useState([74.31071359697442, 31.588167443954312]);
const [featuresLayer, setFeaturesLayer] = useState();
const [point, setPoint] = useState();
const [feature, setFeature] = useState();
const [transformedCentre] = 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;
// initialize map on first render - logic formerly put into componentDidMount
useEffect(() => {
console.log("Initialised for the first time");
console.log("Initial Centre used: " + transformedCentre);
// 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 the initial view
const initialView = new View({
center: transformedCentre,
zoom: 17,
});
// create map
const locationMap = new Map({
target: mapRef.current,
layers: [
new TileLayer({
source: new OSM(),
}),
initalFeaturesLayer,
],
view: initialView,
controls: [],
});
setMap(locationMap);
setFeaturesLayer(initalFeaturesLayer);
}, []);
// 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])];
var newTransformedCentre = transformCentre(newCentre);
}
if (map != null) {
point.setCoordinates(newTransformedCentre);
feature.setGeometry(point);
map.setView(
new View({
center: newTransformedCentre,
zoom: 17,
})
);
// set features to map
featuresLayer.setSource(
new VectorSource({
features: [feature],
})
);
}
}, [props.userEnteredLocation]);
//render component
return <div ref={mapRef} className="map-container"></div>;
}
export default BusinessLocationMap;
Upvotes: 2