How do I re-render google map when props change - javascript

I have an application in which I am using google maps. In the map component, I am drawing shapes and using coordinates and components passed by props to render the map and shapes on the map.
However, I want such that when the zones and coordinates change, the map should re-render. with the updated area map and shape.
When I add the zones and coordinates as array dependecies for the useEffect hook, I can see that the component re-render by logging to the console, but the google map does not.
Please, how do I go about this?
Has anyone implemented this before?
Do I have to call the "handleApiLoaded" function again?
My code is shown below.
import GoogleMapReact from "google-map-react";
import useHeightPort from "../../Hooks/useHeightport";
import "../../assets/css/Map.css";
import { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import CreateSelect from "./CreateSelect";
import ZoneInfoWindow from "./ZoneInfoWindow";
import {
createInfoWindow,
drawPolygon,
drawCircle,
fitMapBounds,
deepClone,
} from "./helpers"; /* I am using helper functions to tidy up things, not necessarily
integral to the apps functionality */
function Map({
propZones,
propCoords,
propParticipants,
showPLI
}) {
let participants = deepClone(propParticipants); // get all from a single object here
let [zones, setZones] = useState(deepClone(propZones));
let [coords, setCoords] = useState(deepClone(propCoords));
const [createDropdown, setCreateDropdown] = useState(<></>);
const { Height } = useHeightPort();
const calculatedHeight = Number(Height) - 289;
const defaultProps = {
center: { lat: 0, lng: 0 },
zoom: 0,
};
let globalMap, globalMaps;
let shapeObjs = [];
let activeZoneId = false;
const [markers, setMarkers] = useState([]);
useEffect(() => {
setZones(deepClone(propZones));
setCoords(deepClone(propCoords));
console.log("Something changed");
}, [propCoords, propZones /* zoneGroup_id, reshow */]);
const handleApiLoaded = (map, maps) => {
globalMap = map;
globalMaps = maps;
zones.forEach((zone, i) => {
// Draw Shape
let { shape, lat, lng } = drawShape(zone, coords[i]);
// Add InfoWindow to Polygon
let infoWindow = createIW(zone, lat, lng, shape);
shape.addInfoWindow(infoWindow);
shape.zone_id = zone.zone_id;
shapeObjs.push(shape);
});
// Add Participants to Map
if (participants) {
let icon;
let newMarkers = [];
participants.forEach((p, i) => {
icon = {
url: p.image,
//size: new maps.Size(40, 40),
size: new mapds.Size(40, 40),
};
// let marker = new maps.Marker({
let marker = new maps.Marker({
position: { lat: p.lat, lng: p.lng },
//map,
map,
icon,
visible: showPLI,
optimized: false,
});
// Create InfoWindow
let pliInfoWindow = createPLIIW(marker, "Impossible", p);
newMarkers.push({ marker, infoWindow: pliInfoWindow });
});
setMarkers(newMarkers);
}
fitMapBounds(maps, map, zones, coords, shapeObjs);
setCreateDropdown(<CreateSelect drawNewShape={drawNewShape} />); // To create a new shap when the api is fully loaded
};
// Draw Shape
function drawShape(zone, coord) {
const shape =
zone.shapeType === "polygon"
? drawPolygon(
zone.id,
globalMaps,
globalMap,
coord,
zone.style,
updateZoneCoordinates
)
: drawCircle(
zone.id,
globalMaps,
globalMap,
coord,
zone.style,
updateZoneCoordinates
);
let { lat, lng } =
zone.shapeType === "polygon"
? coord[0]
: { lat: coord.lat, lng: coord.lng };
return { shape, lat, lng };
}
// Create InfoWindow
function createIW(zone, lat, lng, shape) {
// Create InfoWindow
let infoDetails = {
loc: zone.location,
lat,
lng,
};
let infoDiv = document.createElement("div");
let infoContent = ReactDOM.render(
ZoneInfoWindow(zone.id, zone.title, infoDetails),
infoDiv
);
return createInfoWindow(infoContent, zone.id, globalMaps, {
parent: shape,
delete: deleteZone,
edit: editZoneWithForm,
});
}
// Draw new shape
const drawNewShape = (type) => {
let newZone = {
id: zones.length + 1,
title: "New Zone",
location: "Atlanta, Georgia",
shapeType: type,
style: {
strokeColor: "#0072ff",
strokeOpacity: 1,
strokeWeight: 2,
fillColor: "#FFFFFF",
fillOpacity: 0.4,
},
};
let newCoord = drawDefaultShape(type);
let { shape, lat, lng } = drawShape(newZone, newCoord);
let infoWindow = createIW(newZone, lat, lng, shape);
shape.addInfoWindow(infoWindow);
shape.obj.setEditable(true);
zones.push(newZone);
coords.push(newCoord);
shapeObjs.push(shape);
if (activeZoneId !== false && activeZoneId !== newZone.id) {
let activeZone = zones.find((z) => z.id === activeZoneId);
shapeObjs[zones.indexOf(activeZone)].obj.setEditable(false);
}
activeZoneId = newZone.id;
editZoneWithForm(newZone.id);
};
return (
<div className="map-container" style={{ height: calculatedHeight + 159 }}>
<div className="map-components" style={{ height: calculatedHeight - 70 }}>
<div className="map-create-zone-container">{createDropdown}</div>
<GoogleMapReact
bootstrapURLKeys={{ key: "GOOGLE_API_KEY" }} // This will be turned to an Environment variable when we push live
defaultCenter={defaultProps.center}
defaultZoom={defaultProps.zoom}
yesIWantToUseGoogleMapApiInternals
/* onGoogleApiLoaded={({ map, maps }) =>
handleApiLoaded.current(map, maps)
} */
onGoogleApiLoaded={({map, maps}) => handleApiLoaded(map, maps)}
></GoogleMapReact>
</div>
</div>
);
}
export default Map;

Related

Google Maps React Wrapper - Marker Cluster create #googlemaps/react-wrapper

I am using the Google ReactJS library to add Maps to my React web app and the #googlemaps/react-wrapper library to cluster markers. But I am not able to make marker clustering on the wrapper. Please, if anyone has any idea, help to solve the problem.
The component code is present below:
import React, { useEffect, useRef } from "react";
import { Wrapper, Status } from "#googlemaps/react-wrapper";
export default function MapContainer({ center, zoomLevel, markerList, icon }) {
const googleMapRef = useRef(null);
let googleMap = null;
useEffect(() => {
try {
let bounds = new window.google.maps.LatLngBounds();
const initGoogleMap = () => {
return new window.google.maps.Map(googleMapRef.current, {
center: center,
zoom: zoomLevel,
styles: [{ stylers: [{ saturation: -100 }] }]
});
}
const createMarker = (markerObj) => new window.google.maps.Marker({
position: { lat: parseFloat(markerObj.lat), lng: parseFloat(markerObj.lng) },
map: googleMap,
icon: {
url: icon,
scaledSize: new window.google.maps.Size(80, 80)
},
});
googleMap = initGoogleMap();
markerList.map(x => {
const marker = createMarker(x);
bounds.extend(marker.position);
});
if (markerList.length === 0) {
initGoogleMap();
bounds = new window.google.maps.LatLngBounds();
}
googleMap.fitBounds(bounds);
}
catch (e) {
console.log("maps", e)
}
})
const render = (status) => {
if (status === Status.LOADING) return "Loading...";
if (status === Status.FAILURE) return "Error";
return null;
};
return (
<div>
<div>
<Wrapper apiKey={TOKEN} render={render}>
{/* <GISTrackingMap zoomLevel={props.zoomLevel} mapFilterByVal={props.mapFilterByVal} center={props.center} gisTrackingData={props.gisTrackingData} /> */}
<div ref={googleMapRef} style={{ width: "100%", height: "78vh" }} />
</Wrapper>
</div>
</div>
)
}
To create a clusterer you have to have an array of markers. So I would suggest moving bounds.extend(marker.position); out of the markerList mapping and save the result:
const markers = markerList.map(x => {
createMarker(x);
});
And then just create a clusterer:
new MarkerClusterer({ googleMapRef, markers });
UPD
Looks like the MarkerClusterer doesn't work with ref, so the code above should be changed as follows:
new MarkerClusterer({ googleMap, markers });
Don't forget to install the library and add the import:
yarn add #googlemaps/markerclusterer
import { MarkerClusterer } from '#googlemaps/markerclusterer';

Markers in react-google-map doesnt render after change in props

I cant deal with that problem.
I got object locations. there are objects with info about position, function and icon color.
I send it to Map. At start it shows ok but then when i filter the array and send new ones to show it doesnt render new ones.
I add state to class and setState it- after check i see in console that everything is ok with changes- but markers are still the same.
Please help
locations is also a state in parent stateless component.
Map Component
import React, {Component} from 'react'
import GoogleMapReact from 'google-map-react'
import MarkerClusterer from '#google/markerclusterer'
export default class GoogleMapContainer extends Component {
state = {
locationsToShow: null
}
componentDidMount () {
const script = document.createElement('script')
script.src = 'https://developers.google.com/maps/documentation/javascript/examples /markerclusterer/markerclusterer.js'
script.async = true
document.body.appendChild(script)
}
componentDidUpdate(prevProps) {
if (this.props.locations !== prevProps.locations) {
this.setState({locationsToShow: this.props.locations})
}
}
static defaultProps = {
center: {
lat: 59.95,
lng: 30.33
},
zoom: 11
}
render () {
const setGoogleMapRef = (map, maps) => {
this.googleMapRef = map
this.googleRef = maps
let locations = this.props.locations
console.log('locations w propsie')
let markers = this.state.locationsToShow && this.state.locationsToShow.map((location) => {
let item = new this.googleRef.Marker({position: location.position, icon: location.icon })
google.maps.event.addListener(item, 'click', location.fn)
return item
})
console.log('markerow jest')
let markerCluster = new MarkerClusterer(map, markers, {
imagePath: 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m',
gridSize: 60,
minimumClusterSize: 2
})
}
return (
<GoogleMapReact
bootstrapURLKeys={{key: `apiKey`}}
yesIWantToUseGoogleMapApiInternals
onGoogleApiLoaded={({map, maps}) => setGoogleMapRef(map, maps)}
defaultCenter={{lat: 52.193275, lng: 20.930372}}
defaultZoom={7}
options={{streetViewControl: true}}
/>
)
}
}
parent comp element
let locationsList = filteredData.map((el) => {
const lati = Number(el.location.latitude).toFixed(6)
const longi = Number(el.location.longitude).toFixed(6)
return {position: { lat: Number(lati), lng: Number(longi) }, icon: typeOfIcon(el.status), fn: ()=>{
setIsOpen(true)
setData(el)
} }
})
setLocations(locationsList)
setGoogleMapRef is only called when the map component is instantiated I am guessing (you want this). so you need to access the map and update your locations in your render function outside of the setGoogleMapRef callback.
something like the following:
render () {
const setGoogleMapRef = (map, maps) => {
this.googleMapRef = map
this.googleRef = maps
this.markerCluster = //...
};
if (this.googleMapRef) {
let locations = this.props.locations
console.log('locations w propsie')
let markers = this.state.locationsToShow &&
this.state.locationsToShow.map((location) => {
let item = new this.googleRef.Marker({position: location.position, icon: location.icon })
google.maps.event.addListener(item, 'click', location.fn)
return item
})
console.log('markerow jest')
// update marker cluster
}
return //...;
}

How to use Google Map MarkerClusterer on OverlayView which is rendered via ReactDOM createPortal

I've been banging my head against the wall with google maps. See my initial issue here
I fixed the issue of using a react component as a map marker, by creating a portal with the OverlayView into a div. But now I'm faced with a bump in a rug now where I am now unsure how to create clustered markers?
Previously I used #googlemaps/markerclustererplus to create a cluster from an array of of markers created from a class CustomMarker that generates an OverlayView with the code snippet below.
const markers = mapMarkers.map((m) => {
const { position, icon, draggable } = m;
const marker = new CustomMarker(
position,
draggable,
icon,
map,
onChildMouseUp
);
return marker;
});
// Add a marker clusterer to manage the markers.
new MarkerClusterer(map, markers, {
calculator: (gMarkers, numStyles) => {
let index = 0;
const count = gMarkers.length;
let dv = count;
while (dv !== 0) {
dv = parseInt(dv / 10, 10);
index += 1;
}
index = Math.min(index, numStyles);
return {
text: `${count}`,
index
};
},
gridSize: 50,
styles: [
{
className: "clusterMarker",
width: 50,
height: 18
}
]
});
Now I'm confused how to populate the 2nd argument in MarkerClusterer with the markers now I add markers in the components return statement
<Map id="map" mapHeight={height} mapWidth={width}>
{gmap && mapMarkers?.length > 0 ? (
<>
{mapMarkers.map((m, i) => {
return (
<CustomMarkerNew key={i} position={m.position} googleMap={gmap}>
{m.icon}
</CustomMarkerNew>
);
})}
</>
) : null}
</Map>
This is how the CustomMarkerNew component looks
CustomMarkerNew.js
import React, { useState, useEffect, useCallback } from "react";
import ReactDOM from "react-dom";
const OverlayView = (props) => {
const { google } = window;
const { googleMap, position, children } = props;
const [latlng, setLatlng] = useState(position);
const [container, setContainer] = useState(document.createElement("div"));
const [overlay, setOverlay] = useState(undefined);
const initOverlay = useCallback(() => {
const overlayView = new google.maps.OverlayView();
overlayView.setMap(googleMap);
overlayView.getDraggable = () => true;
setOverlay(overlayView);
setLatlng(position);
google.maps.OverlayView.preventMapHitsFrom(container);
}, [googleMap, container]);
const onAdd = useCallback(() => {
// only draw marker if it has not been assigned the customMarker class
if (!container.classList.contains("customMarker")) {
container.classList.add("customMarker");
container.style.position = "absolute";
const panes = overlay.getPanes();
panes.overlayImage.appendChild(container);
}
overlay.setPosition();
}, [overlay]);
const draw = useCallback(() => {
container.style.position = "absolute";
container.draggable = true;
overlay.getPanes().floatPane.appendChild(container);
overlay.setPosition();
}, [overlay, container, latlng]);
const onRemove = useCallback(() => {
ReactDOM.unmountComponentAtNode(container);
setContainer(undefined);
overlay.setMap(null);
}, [container, overlay]);
const setPosition = (pos) => {
// update global latlng value with current position of marker
if (pos) setLatlng(pos);
const projection = overlay.getProjection();
if (!projection) return;
// convert latlng value to pixel equivalent
const point = projection.fromLatLngToDivPixel(latlng);
// set left and top values from values from point variable to place marker correctly on map
if (point && point.x && point.y) {
container.style.left = `${point.x}px`;
container.style.top = `${point.y}px`;
}
};
const bindFunctions = useCallback(() => {
overlay.onAdd = onAdd.bind(this);
overlay.draw = draw.bind(this);
overlay.onRemove = onRemove.bind(this);
overlay.setPosition = setPosition.bind(this);
overlay.getDraggable = () => true;
}, [overlay]);
useEffect(() => {
if (overlay) {
bindFunctions();
}
}, [overlay]);
useEffect(() => {
initOverlay();
}, []);
return (
(overlay &&
children &&
container &&
ReactDOM.createPortal(children, container)) ||
null
);
};
export default React.memo(OverlayView);
Here is a CodeSandbox as an example
Any help would be much appreciated.

Google Map Integration with React Js projects

Need to use Google Maps API to get this as desired output: {'hotels','atms','banks','hospitals'}.
Already tried google-map-react but not worked properly.
Requirement it can be any of the below:
free api
script
library.
Reference Image:
You can use Google Maps Javascript's Places Nearby Search to search for the nearby places using different keyword as you mentioned. Here is a sample code from Google Maps Doc.
You can also implement it in reactjs using the code snippet below with the similar functionality as your use case.
import React from "react";
import ReactDOM from "react-dom";
import "./style.css";
let map;
const API_KEY = "YOUR_API_KEY";
const coords = { lat: 41.375885, lng: 2.177813 };
let markers = [];
class NearbySearchApp extends React.Component {
constructor(props) {
super(props);
this.renderMap = this.renderMap.bind(this);
this.handleClick = this.handleClick.bind(this);
}
componentDidMount() {
if (!window.google) {
const script = document.createElement("script");
script.type = "text/javascript";
script.src =
`https://maps.googleapis.com/maps/api/js?key=` +
API_KEY +
`&libraries=geometry,places`;
script.id = "googleMaps";
script.async = true;
script.defer = true;
document.body.appendChild(script);
script.addEventListener("load", (e) => {
this.renderMap();
});
} else {
this.renderMap();
}
}
renderMap() {
const el = document.getElementById("map");
if (el) {
map = new google.maps.Map(el, {
zoom: 14,
center: {
lat: coords.lat,
lng: coords.lng,
},
});
return map;
} else {
return null;
}
}
handleClick(data) {
//clearing markers and marker array everytime a keyword is clicked
for (let i = 0; i < markers.length; i++) {
markers[i].setMap(null);
}
markers = [];
document.getElementById("chosen").innerHTML =
"You clicked: " + data.keyword;
//NearbySearch function
let service = new google.maps.places.PlacesService(map);
service.nearbySearch(
{ location: coords, radius: 500, type: [data.keyword] },
function (results, status, pagination) {
if (status !== "OK") return;
const bounds = new google.maps.LatLngBounds();
for (let i = 0, place; (place = results[i]); i++) {
//creating markers icon per type of place
let image = {
url: place.icon,
size: new google.maps.Size(71, 71),
origin: new google.maps.Point(0, 0),
anchor: new google.maps.Point(17, 34),
scaledSize: new google.maps.Size(25, 25),
};
//creates marker for every place result
const marker = new google.maps.Marker({
map: map,
icon: image,
title: place.name,
position: place.geometry.location,
});
//putting markers in the array
markers.push(marker);
//showing markers from the array in the map
for (let i = 0; i < markers.length; i++) {
markers[i].setMap(map);
}
bounds.extend(place.geometry.location);
}
}
);
}
render() {
return (
<section>
<h1>ADD YOUR API KEY TO MAKE IT WORK</h1>
<div id="map" />
<div id="panel">
<h3 id="chosen">Please choose a Place:</h3>
<input
type="button"
onClick={this.handleClick.bind(null, { keyword: "atm" })}
value="ATM"
/><br/>
<input
type="button"
onClick={this.handleClick.bind(null, { keyword: "hospital" })}
value="Hospital"
/><br/>
<input
type="button"
onClick={this.handleClick.bind(null, { keyword: "store" })}
value="store"
/>
</div>
</section>
);
}
}
export default NearbySearchApp;
You can use the export default to export NearbySearchApp and import it to another script file like this:
import React from 'react';
import ReactDOM from 'react-dom';
import NearbySearchApp from './NearbySearchApp';
ReactDOM.render(<NearbySearchApp />, document.getElementById('app'));
Here's a sample code that implements this. Make sure to use your API key for the sample code to work properly.

Google is not defined using react google maps?

I have read the other questions related to this and tried implementing what the answers were on there.
They recommended adding /*global google */ - did not work
Next was to add const google = window.google; - did not work
I will post the code below and the error.
The error is happening where new google.maps is being called
Map.js:
/* global google */
import { default as React, Component } from 'react';
import raf from 'raf';
import canUseDOM from 'can-use-dom';
const google = window.google;
import { withGoogleMap, GoogleMap, Circle, InfoWindow, Marker } from 'react-google-maps';
import withScriptjs from 'react-google-maps/lib/async/withScriptjs';
const googleMapURL =
'https://maps.googleapis.com/maps/api/js?v=3.27&libraries=places,geometry&key=AIzaSyA7XEFRxE4Lm28tAh44M_568fCLOP_On3k';
const geolocation =
canUseDOM && navigator.geolocation
? navigator.geolocation
: {
getCurrentPosition(success, failure) {
failure("Your browser doesn't support geolocation.");
},
};
const GeolocationExampleGoogleMap = withScriptjs(
withGoogleMap(props =>
<GoogleMap defaultZoom={8} center={props.center}>
{props.center &&
<InfoWindow position={props.center}>
<div>User's Location</div>
</InfoWindow>}
{props.center &&
<Circle
center={props.center}
radius={props.radius}
options={{
fillColor: 'red',
fillOpacity: 0.2,
strokeColor: 'red',
strokeOpacity: 1,
strokeWeight: 1,
}}
/>}
>
{props.markers.map((marker, index) => {
const onClick = () => props.onMarkerClick(marker);
const onCloseClick = () => props.onCloseClick(marker);
return (
<Marker
key={index}
position={marker.position}
title={(index + 1).toString()}
onClick={onClick}
>
{marker.showInfo &&
<InfoWindow onCloseClick={onCloseClick}>
<div>
<strong>
{marker.content}
</strong>
<br />
<em>The contents of this InfoWindow are actually ReactElements.</em>
</div>
</InfoWindow>}
</Marker>
);
})}
</GoogleMap>,
),
);
function generateInitialMarkers() {
const southWest = new google.maps.LatLng(-31.203405, 125.244141);
const northEast = new google.maps.LatLng(-25.363882, 131.044922);
const lngSpan = northEast.lng() - southWest.lng();
const latSpan = northEast.lat() - southWest.lat();
const markers = [];
for (let i = 0; i < 5; i++) {
const position = new google.maps.LatLng(
southWest.lat() + latSpan * Math.random(),
southWest.lng() + lngSpan * Math.random(),
);
markers.push({
position,
content: 'This is the secret message'.split(' ')[i],
showInfo: false,
});
}
return markers;
}
export default class GeolocationExample extends Component {
constructor(props) {
super(props);
super(props);
this.state = {
center: null,
content: null,
radius: 6000,
markers: generateInitialMarkers(),
};
const isUnmounted = false;
handleMarkerClick = this.handleMarkerClick.bind(this);
handleCloseClick = this.handleCloseClick.bind(this);
}
handleMarkerClick(targetMarker) {
this.setState({
markers: this.state.markers.map((marker) => {
if (marker === targetMarker) {
return {
...marker,
showInfo: true,
};
}
return marker;
}),
});
}
handleCloseClick(targetMarker) {
this.setState({
markers: this.state.markers.map((marker) => {
if (marker === targetMarker) {
return {
...marker,
showInfo: false,
};
}
return marker;
}),
});
}
componentDidMount() {
const tick = () => {
if (this.isUnmounted) {
return;
}
this.setState({ radius: Math.max(this.state.radius - 20, 0) });
if (this.state.radius > 200) {
raf(tick);
}
};
geolocation.getCurrentPosition(
(position) => {
if (this.isUnmounted) {
return;
}
this.setState({
center: {
lat: position.coords.latitude,
lng: position.coords.longitude,
},
content: 'Location found using HTML5.',
});
raf(tick);
},
(reason) => {
if (this.isUnmounted) {
return;
}
this.setState({
center: {
lat: 60,
lng: 105,
},
content: `Error: The Geolocation service failed (${reason}).`,
});
},
);
}
componentWillUnmount() {
this.isUnmounted = true;
}
render() {
return (
<GeolocationExampleGoogleMap
googleMapURL={googleMapURL}
loadingElement={<div style={{ height: '100%' }}>loading...</div>}
containerElement={<div style={{ height: '100%' }} />}
mapElement={<div style={{ height: '100%' }} />}
center={this.state.center}
content={this.state.content}
radius={this.state.radius}
onMarkerClick={this.handleMarkerClick}
onCloseClick={this.handleCloseClick}
markers={this.state.markers}
/>
);
}
}
You are loading async script. When you initialize google constant there is no google object. You can solve this by using 'ref' for map component. Ref callback fired when google object exists. For example:
<GoogleMap defaultZoom={8} center={props.center} ref={()=>{props.onMapLoad}}>...</GoogleMap>
...
constructor(props) {
...
this.onMapLoad = this.onMapLoad.bind(this)
}
...
onMapLoad() {
/*init markers and all objects with "google" here*/
}
...
<GeolocationExampleGoogleMap
onMapLoad={this.onMapLoad}
...
/>
and, of course, do not init google const, use just google object instead,

Categories