Limit the number of clicks on map using React - reactjs

I am using the Google Maps API and I want to limit the number of markers on the map (limit: 10). I couldn't find anything related to it in the API docs
neither I can find any similar source to solve my problem.
Here is my code:
import React from "react";
import {
GoogleMap,
useLoadScript,
Marker,
} from "#react-google-maps/api";
const mapContainerStyle = {
height: "50vh",
width: "100vw",
};
const options = {
zoomControl: false,
scrollwheel: false,
draggable: false
};
const center = {
lat: 34.155834,
lng: -119.202789,
};
export default function App() {
const { isLoaded, loadError } = useLoadScript({
googleMapsApiKey: "AIzaSyCpaQDSgGTCetTR0uz42RyV80cByaGaYLs",
});
const [markers, setMarkers] = React.useState([]);
const onMapClick =
React.useCallback((e) => {
setMarkers((current) =>
[
...current,
{
lat: e.latLng.lat(),
lng: e.latLng.lng(),
},
]);
}, []);
const mapRef = React.useRef();
const onMapLoad = React.useCallback((map) => {
mapRef.current = map;
}, []);
if (loadError) return "Error";
if (!isLoaded) return "Loading...";
return (
<div>
<GoogleMap
id="map"
mapContainerStyle={mapContainerStyle}
zoom={14}
center={center}
options={options}
onClick={onMapClick}
onLoad={onMapLoad}
>
{markers.map((marker) => (
<Marker
key={`${marker.lat}-${marker.lng}`}
position={{ lat: marker.lat, lng: marker.lng }}
onClick={() => {
console.log("clicked")
}}
/>
))}
</GoogleMap>
</div>
);
}
How do I set the number of clicks up to 10?

You could do something like this. You may also want to call another function before returning current to perform some other update to alert the user they are maxed out on markers.
const onMapClick = React.useCallback((e) => {
setMarkers((current) => {
if (current.length < 10) {
return [
...current,
{
lat: e.latLng.lat(),
lng: e.latLng.lng()
}
];
} else {
return current;
};
});
}, []);

Related

Map & Marker nothing showing white page #react-google-maps/api on localhost

import React from 'react'
import { GoogleMap, Marker, useJsApiLoader } from '#react-google-maps/api';
const containerStyle = {
width: '95%',
height: '550px'
};
const center = {
lat: 27.8103,
lng: 70.4125
};
function Map(props) {
const { isLoaded } = useJsApiLoader({
id: 'google-map-script',
googleMapsApiKey: "API KEY"
})
const [map, setMap] = React.useState(null)
const onLoad = React.useCallback(function callback(map) {
const bounds = new window.google.maps.LatLngBounds({
lat: props.lat,
lng: props.lng
});
map.fitBounds(bounds);
setMap(map)
}, [])
const onUnmount = React.useCallback(function callback(map) {
setMap(null)
}, [])
return isLoaded ? (
<GoogleMap
mapContainerStyle={containerStyle}
center={center}
zoom={20}
onLoad={onLoad}
onUnmount={onUnmount}>
<></>
<Marker onLoad={onLoad} position={center} />
</GoogleMap>
) : <></>
}
export default Map
I tried pinning the marker to the location but the map is now not showing as the marker is not showing. Totally blank page

How to use MarkerClusterer with #googlemaps/react-wrapper

After following this example provided by google for adding a map and markers to a react application using #googlemaps/react-wrapper, I decided to try adding marker clustering to my app. However, I am unable to get the clusters to appear.
I tried following this question/answer but no clusters have appeared despite adding new MarkerClusterer({ ref, markers }); to my code:
App.js
/*global google*/
import { useState, useEffect, useRef } from "react";
import { Wrapper } from "#googlemaps/react-wrapper";
import { MarkerClusterer } from "#googlemaps/markerclusterer";
import { createCustomEqual } from "fast-equals";
const markerList = [
{ id: "A12345", uuid: "500924cf83424aad9e7d386bbec88ef6", lat: 44.459744, lng: -73.214126, assetName: "A" },
{ id: "B09423", uuid: "500924cf83424aad9e7d386bbec88ef6", lat: 44.465291, lng: -73.190723, assetName: "A" },
{ id: "C98765", uuid: "c0385833-e483-40d1-803f-2b4c26ae3799", lat: 44.476949, lng: -73.210578, assetName: "B" },
{ id: "D99999", uuid: "c0385833-e483-40d1-803f-2b4c26ae3799", lat: 44.444572, lng: -73.208741, assetName: "B" },
{ id: "E12345", uuid: "500924cf83424aad9e7d386bbec88ef6", lat: 38.459744, lng: -81.214126, assetName: "A" },
{ id: "F09423", uuid: "500924cf83424aad9e7d386bbec88ef6", lat: 38.465291, lng: -81.190723, assetName: "A" },
{ id: "G98765", uuid: "c0385833-e483-40d1-803f-2b4c26ae3799", lat: 38.476949, lng: -81.210578, assetName: "B" },
{ id: "H99999", uuid: "c0385833-e483-40d1-803f-2b4c26ae3799", lat: 38.444572, lng: -81.208741, assetName: "B" },
]
const render = (status) => {
return <h1>{status}</h1>;
};
const App = () => {
const [zoom, setZoom] = useState(8); // initial zoom
const [center, setCenter] = useState({ lat: 44.45, lng: -73.21 });
const onIdle = (m) => {
console.log("onIdle");
};
return (
<>
<div style={{ width: "500px", height: "500px" }}>
<Wrapper
apiKey={process.env.REACT_APP_GOOGLE_MAPS_API_KEY}
render={render}
>
<Map
center={center}
onIdle={onIdle}
zoom={zoom}
style={{ flexGrow: "1", height: "100%" }}
/>
</Wrapper>
</div>
</>
);
};
const Map = ({ onIdle, children, style, ...options }) => {
const ref = useRef(null);
const [map, setMap] = useState();
useEffect(() => {
if (ref.current && !map) {
setMap(new window.google.maps.Map(ref.current, {}));
}
// Add some markers to the map.
const markers = markerList.map((m) => {
return new window.google.maps.Marker({
position: { lat: parseFloat(m.lat), lng: parseFloat(m.lng) }
//map: map,
});
});
// Add a marker clusterer to manage the markers.
new MarkerClusterer({ ref, markers });
}, [ref, map]);
useDeepCompareEffectForMaps(() => {
if (map) {
map.setOptions(options);
}
}, [map, options]);
useEffect(() => {
if (map) {
["click", "idle"].forEach((eventName) =>
google.maps.event.clearListeners(map, eventName)
);
if (onIdle) {
map.addListener("idle", () => onIdle(map));
}
}
}, [map, onIdle]);
return (
<>
<div ref={ref} style={style} />
</>
);
};
const deepCompareEqualsForMaps = createCustomEqual((deepEqual) => (a, b) => {
if (a instanceof google.maps.LatLng || b instanceof google.maps.LatLng) {
return new google.maps.LatLng(a).equals(new google.maps.LatLng(b));
}
return deepEqual(a, b);
});
function useDeepCompareMemoize(value) {
const ref = useRef();
if (!deepCompareEqualsForMaps(value, ref.current)) {
ref.current = value;
}
return ref.current;
}
function useDeepCompareEffectForMaps(callback, dependencies) {
useEffect(callback, dependencies.map(useDeepCompareMemoize));
}
export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
How can I get MarkerClusterer to function with #googlemaps/react-wrapper?
Replace
new MarkerClusterer({ ref, markers });
with
new MarkerClusterer({ map, markers });
PS: The answer to the mentioned question was updated

React google maps/api: How to focus on the marker position with zoom based on search results?

I need to focus on the marker based on dynamic search results. As far as I understood from other discussions, what I need is to make a use of panTo function.
I gave a try, and it works partially.
Here is the code example and the codesandbox link
Note: Provide your Google Maps key in .env file to test the codesandbox.
Map.js
import { GoogleMap, LoadScript, Marker } from "#react-google-maps/api";
import React, { useEffect } from "react";
const containerStyle = {
width: "100%",
height: "500px"
};
const center = { lat: 45.4211, lng: -75.6903 };
const Maps = ({ filteredPark }) => {
const key = process.env.REACT_APP_GOOGLE_KEY; //test with your key please
if (!key) {
throw new Error("Google token is not set");
}
const mapRef = React.useRef();
const onMapLoad = React.useCallback((map) => {
mapRef.current = map;
}, []);
const panTo = React.useCallback(({ lat, lng }) => {
mapRef?.current?.panTo({ lat, lng });
mapRef?.current?.setZoom(18);
}, []);
useEffect(() => {
filteredPark.map((a) => {
const lat = a.coordinates[1];
const lng = a.coordinates[0];
return panTo({ lat, lng });
});
}, [filteredPark, panTo]);
return (
<div style={{ width: "100%", height: "100%" }}>
<LoadScript googleMapsApiKey={key}>
<GoogleMap
mapContainerStyle={containerStyle}
center={center}
zoom={6}
onLoad={onMapLoad}
>
{filteredPark &&
filteredPark.map((park) => (
<Marker
key={park.PARK_ID}
position={{
lat: park.coordinates[1],
lng: park.coordinates[0]
}}
/>
))}
</GoogleMap>
</LoadScript>
</div>
);
};
export default React.memo(Maps);
custom search hook component
import { useMemo } from "react";
import * as parkData from "./data/skateboard-parks.json";
const useParkFIlter = (DESCRIPT_1) => {
const gridRows = parkData.features.map((park) => park.properties);
const filteredParks = useMemo(() => {
return (
gridRows &&
gridRows.filter((dispo) =>
dispo.DESCRIPT_1.toLowerCase().includes(DESCRIPT_1.toLowerCase())
)
);
}, [DESCRIPT_1, gridRows]);
return [filteredParks];
};
export default useParkFIlter;
Any help will be appreciated.
I have made changes in your code. If it doesn't work, do let me know.
import { GoogleMap, LoadScript, Marker } from "#react-google-maps/api";
import React, { useEffect } from "react";
const containerStyle = {
width: "100%",
height: "500px"
};
const center = { lat: 45.4211, lng: -75.6903 };
const Maps = ({ filteredPark }) => {
const key = process.env.REACT_APP_GOOGLE_KEY; //test with your key please
if (!key) {
throw new Error("Google token is not set");
}
//const mapRef = React.useRef();
const [mapInstance, setMapInstance] = useState(null);
const[zoom, setZoom] = useState(6);
const onMapLoad = React.useCallback((map) => {
//mapRef.current = map;
setMapInstance(map);
}, []);
//const panTo = React.useCallback(({ lat, lng }) => {
//mapRef?.current?.panTo({ lat, lng });
//mapRef?.current?.setZoom(18);
//}, []);
useEffect(()=>{
if(mapInstance){
mapInstance.panTo({ lat, lng });
setZoom(18);
}
},[mapInstance]);
useEffect(() => {
filteredPark.map((a) => {
const lat = a.coordinates[1];
const lng = a.coordinates[0];
return panTo({ lat, lng });
});
}, [filteredPark, panTo]);
return (
<div style={{ width: "100%", height: "100%" }}>
<LoadScript googleMapsApiKey={key}>
<GoogleMap
mapContainerStyle={containerStyle}
center={center}
//zoom={6}
zoom={zoom}
onLoad={onMapLoad}
>
{filteredPark &&
filteredPark.map((park) => (
<Marker
key={park.PARK_ID}
position={{
lat: park.coordinates[1],
lng: park.coordinates[0]
}}
/>
))}
</GoogleMap>
</LoadScript>
</div>
);
};
export default React.memo(Maps);

Centralize google map issue

import React, {
useState
} from "react";
import GoogleMapReact from "google-map-react";
function GMap() {
const [latLgn, setLatLgn] = useState([{
lng: 24.7536,
lat: 59.437
},
{
lng: 24.7303,
lat: 59.4393
},
{
lng: 24.7387,
lat: 59.4497
},
]);
const [tallinn] = useState({
center: { // where i want to be centerd
lng: 24.7536,
lat: 59.437,
},
zoom: 10,
});
// Fit map to its bounds after the api is loaded
const apiIsLoaded = (map, maps, latlgn) => {
// Get bounds by our latlgn
const bounds = getMapBounds(map, maps, latlgn);
// Fit map to bounds
map.fitBounds(bounds);
// Bind the resize listener
bindResizeListener(map, maps, bounds);
};
// Re-center map when resizing the window
const bindResizeListener = (map, maps, bounds) => {
maps.event.addDomListenerOnce(map, "idle", () => {
maps.event.addDomListener(window, "resize", () => {
map.fitBounds(bounds);
});
});
};
// Return map bounds based on list of places
const getMapBounds = (map, maps, pins) => {
const bounds = new maps.LatLngBounds();
pins.forEach((pin) => {
bounds.extend(new maps.LatLng(pin[1], pin[0]));
});
return bounds;
};
return ( <
div >
<
div style = {
{
height: "100vh",
width: "100%"
}
} >
<
GoogleMapReact bootstrapURLKeys = {
{
key: AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk
}
}
defaultCenter = {
tallinn.center
}
defaultZoom = {
tallinn.zoom
}
onGoogleApiLoaded = {
({
map,
maps
}) => apiIsLoaded(map, maps, latLgn)
}
yesIWantToUseGoogleMapApiInternals >
{
latLgn.map((item, index) => ( <
div lat = {
item[1]
}
lng = {
item[0]
}
key = {
index
} >
</div>
))
} </GoogleMapReact>
</div>
</div>
);
}
export default Gmap
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
I have a problem with centralizing the map I am rendering.
although I have a state with the "lat" and "lng" as stated in the docs still, when I run the app with npm start or refreshing the page, it will centre itself somewhere in the ocean.\
PS. I will paste my "map" component only.
import React, { useState } from "react";
import GoogleMapReact from "google-map-react";
export default function GMap() {
const [latLgn, setLatLgn] = useState([{
lng: 24.7536,
lat: 59.437
},
{
lng: 24.7303,
lat: 59.4393
},
{
lng: 24.7387,
lat: 59.4497
},
]);
const [tallinn] = useState({
center: { // where i want to be centerd
lng: 24.7536,
lat: 59.437,
},
zoom: 10,
});
// Fit map to its bounds after the api is loaded
const apiIsLoaded = (map, maps, latlgn) => {
// Get bounds by our latlgn
const bounds = getMapBounds(map, maps, latlgn);
// Fit map to bounds
map.fitBounds(bounds);
// Bind the resize listener
bindResizeListener(map, maps, bounds);
};
// Re-center map when resizing the window
const bindResizeListener = (map, maps, bounds) => {
maps.event.addDomListenerOnce(map, "idle", () => {
maps.event.addDomListener(window, "resize", () => {
map.fitBounds(bounds);
});
});
};
// Return map bounds based on list of places
const getMapBounds = (map, maps, pins) => {
const bounds = new maps.LatLngBounds();
pins.forEach((pin) => {
bounds.extend(new maps.LatLng(pin[1], pin[0]));
});
return bounds;
};
return (
<div>
<div style={{ height: "100vh", width: "100%" }}>
<GoogleMapReact
bootstrapURLKeys={{key: AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk }}
defaultCenter={tallinn.center}
defaultZoom={tallinn.zoom}
onGoogleApiLoaded={({ map, maps }) => apiIsLoaded(map, maps, latLgn)}
yesIWantToUseGoogleMapApiInternals
>
{latLgn.map((item, index) => (
<div lat={item[1]} lng={item[0]} key={index}< </div>
))}
</GoogleMapReact>
</div>
</div>
);
}
ps. let me know if I should provide more information
UPDATE
I have created a sample project here.
I checked your code and I noticed a couple of things.
First, in your getMapBounds function, the bounds returned are null. This is because the values of pin[1] and pin[0] are undefined. This might be the reasoon why the map is centering in the middle of the ocean like this as this is the center of the world and coordinates are 0,0. You must use pin.lat and pin.lng instead so that it will correctly populate the value for bounds.
Second, it seems that you would like to put markers on the coordinates of your latLgn. To achieve this, you can follow the AnyReactComponent function as mentioned in the google-map-react docs instead of putting a div directly inside the latLgn.map.
Lastly, inside your latLgn.map, you must not use item[1] and item[0] as they are also both undefined instead use item.lat and item.lng.
Here's the code snippet for my working code:
import React, { useState } from "react";
import "./style.css"
import GoogleMapReact from "google-map-react";
const AnyReactComponent = ({ text }) => <div>{text}</div>;
export default function GMap() {
const [latLgn, setLatLgn] = useState([{
lng: 24.7536,
lat: 59.437
},
{
lng: 24.7303,
lat: 59.4393
},
{
lng: 24.7387,
lat: 59.4497
},
]);
const [tallinn] = useState({
center: { // where i want to be centerd
lng: 24.7536,
lat: 59.437,
},
zoom: 10,
});
// Fit map to its bounds after the api is loaded
const apiIsLoaded = (map, maps, latlgn) => {
// Get bounds by our latlgn
const bounds = getMapBounds(map, maps, latlgn);
// Fit map to bounds
map.fitBounds(bounds);
// Bind the resize listener
bindResizeListener(map, maps, bounds);
};
// Re-center map when resizing the window
const bindResizeListener = (map, maps, bounds) => {
maps.event.addDomListenerOnce(map, "idle", () => {
maps.event.addDomListener(window, "resize", () => {
map.fitBounds(bounds);
});
});
};
// Return map bounds based on list of places
const getMapBounds = (map, maps, pins) => {
const bounds = new maps.LatLngBounds();
pins.forEach((pin) => {
bounds.extend(new maps.LatLng(pin.lat, pin.lng));
console.log(pin[0])
});
return bounds;
};
return (
<div>
<div style={{ height: "100vh", width: "100%" }}>
<GoogleMapReact
bootstrapURLKeys={{key: "YOUR_API_KEY" }}
defaultCenter={tallinn.center}
defaultZoom={tallinn.zoom}
onGoogleApiLoaded={({ map, maps }) => apiIsLoaded(map, maps, latLgn)}
yesIWantToUseGoogleMapApiInternals
>
{latLgn.map((item, index) => (
<AnyReactComponent lat={item.lat} lng={item.lng} key={index} text={"(" +item.lat + "," + item.lng +")"}> </AnyReactComponent>
))}
</GoogleMapReact>
</div>
</div>
);
}
I also noticed your question in the comment section. You can use online ides like stackblitz to provide an sscce of your code and just remove the API key. You can put a note in your question that you need to put API key to see how the code works. You can refer to my working code above.

react-google-maps hooks and getBound()

I'm building an application that update the markers each time the user move the map.
I'm using the method onBoundChanges to get the bounds and store them in a hooks (setBounds(mapBounds).)
Unfortunately when I try to store it in a hook with setBounds(mapBounds), its sends the application in a loop.
Do you have any idea how to get around the problem?
Thank you
Here is my main function
import React, { useState, useEffect, Fragment } from "react";
import {
GoogleMap,
LoadScript,
Marker,
InfoWindow,
} from "#react-google-maps/api";
const containerStyle = {
Display: "flex",
height: "100vh",
width: "100%",
};
const divStyle = {
background: `white`,
border: `1px solid #ccc`,
padding: 15,
};
const onLoad = (infoWindow) => {
console.log("infoWindow: ", infoWindow);
};
const position = {
lat: 43.6532,
lng: -79.3832,
};
const center = {
lat: 43.6532,
lng: -79.3832,
};
function ReactMap() {
const [selected, setSelected] = useState(null);
const [bounds, setBounds] = useState(center);
const mapRef = React.useRef();
const onMapLoad = React.useCallback((map) => {
mapRef.current = map;
}, []);
const onMapBoundsChanged = React.useCallback((map) => {
const latlongchange = mapRef.current.getBounds();
const lat = latlongchange.Ua.i;
const lng = latlongchange.Ua.j;
const mapBounds = {
lat: lat,
lng: lng,
};
//setBounds(mapBounds);
console.log(mapBounds);
}, []);
return (
<Fragment>
<LoadScript googleMapsApiKey="">
<GoogleMap
mapContainerStyle={containerStyle}
center={center}
onLoad={onMapLoad}
onBoundsChanged={onMapBoundsChanged}
zoom={14}
>
{selected ? (
<InfoWindow
onLoad={onLoad}
position={position}
onCloseClick={() => {
setSelected(null);
}}
>
<div style={divStyle}>
<h1>La place</h1>
</div>
</InfoWindow>
) : null}
<Marker
className="marker"
position={position}
name="My Marker"
color="blue"
onClick={() => {
setSelected(true);
}}
/>
</GoogleMap>
</LoadScript>
</Fragment>
);
}
export default ReactMap;
Ok. May be you should use additional hook for debouncing? For example:
Step 1: Hook useDebouncedCallback.js
import { useEffect, useRef } from 'react'
export function useDebouncedCallback(callback, wait) {
const argsRef = useRef()
const timeout = useRef()
function cleanup() {
if (timeout.current) clearTimeout(timeout.current)
}
useEffect(() => cleanup, [])
return function debouncedCallback(...args) {
argsRef.current = args
cleanup()
timeout.current = setTimeout(() => {
if (argsRef.current) callback(...argsRef.current)
}, wait)
}
}
Step 2: Your React.FC
// ...
import { useDebouncedCallback } from '#/common/hooks/useDebouncedCallback'
function ReactMap() {
// ...
const onMapBoundsChangedDebounced = useDebouncedCallback(onMapBoundsChanged, 500)
return (
<Fragment>
<LoadScript googleMapsApiKey="">
<GoogleMap
// ...
onBoundsChanged={onMapBoundsChangedDebounced}
// ...
>
Step 3: use constant center as initial state (not empty array)
const position = {
lat: 43.6532,
lng: -79.3832,
};
const center = {
lat: 43.6532,
lng: -79.3832,
};
function ReactMap() {
const [selected, setSelected] = useState(null);
const [bounds, setBounds] = useState(center);
// ...

Resources