React Google Maps unique key error in loop - javascript

I got this react-google-maps component it is working and I receive all the data from the different locations but the console gives me this error message:
Warning: Each child in a list should have a unique "key" prop.
In my component, I've added a unique key tho.. what's the problem?
I import the map component on the page simply like that <Map />.
import React from 'react'
import { GoogleMap, Marker, withGoogleMap, withScriptjs, InfoWindow } from "react-google-maps"
import { useStaticQuery, graphql } from "gatsby"
const Map = () => {
const data = useStaticQuery(graphql`
{
allContentfulHotels {
nodes {
id
title
location {
lat
lon
}
}
}
}
`)
const {
allContentfulHotels: { nodes: locations },
} = data
return (
<>
{locations.map(({ location }) => {
console.log(location);
return (
<GoogleMap
key={location.id}
defaultZoom={15}
defaultCenter={{
lat: location.lat,
lng: location.lon,
}}
>
<Marker
key={location.id}
position={{
lat: location.lat,
lng: location.lon,
}}
>
</Marker>
</GoogleMap>
)
})}
</>
)
}
const MapComponent = withScriptjs(withGoogleMap(Map));
const MyMap = () => (
<div>
<div
style={{ width: '100%' }}
>
<MapComponent
isMarkerShown
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `500px` }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
</div>
</div>
)
export default MyMap
Hope someone knows what the problem is.

For Example: I am Using map function as below and set unique key..
{locations.map(({ location }, index) => {
console.log(location);
console.log(index);
return (
<GoogleMap
key={index}
defaultZoom={15}
defaultCenter={{
lat: location.lat,
lng: location.lon,
}}

Looks like you're specifying the same key for <GoogleMap /> and <Marker /> component, try setting different keys for components:
<GoogleMap
key={`map-${location.id}`}
defaultZoom={15}
defaultCenter={{
lat: location.lat,
lng: location.lon,
}}
>
<Marker
key={`marker-${location.id}`}
position={{
lat: location.lat,
lng: location.lon,
}}
>
</Marker>
</GoogleMap>

Related

unable to add latlng from api to react-google-map

passing as props here
<MyMapComponent
isMarkerShown
googleMapURL={https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places}
loadingElement={<div style={{ height: 100% }} />}
containerElement={<div style={{ height: 400px }} />}
mapElement={<div style={{ height: 100% }} />}
lat={lat}
lang={long}
/>
Map component
import React from 'react'
import { withScriptjs, withGoogleMap, GoogleMap, Marker } from "react-google-maps"
const MyMapComponent = withScriptjs(withGoogleMap((props) => {
return (
< GoogleMap
defaultZoom={5}
defaultCenter={{ lat: props.lat, lng: props.lang }}
>
{props.isMarkerShown && <Marker position={{ lat: props.;at, lng: props.lang }} />}
</GoogleMap >
)
}));
export default MyMapComponent;

Error "Cannot read property 'map' of undefined" after json fetching

After fetching a json file from server keep getting following mistake
TypeError: Cannot read property 'map' of undefined
I understand that have problems with locationsData, which I'm fetching, but I assumed that it already has an array format due to the format of the parsed data (example, please, see below).
import React, { useState } from "react";
import {
withGoogleMap,
withScriptjs,
GoogleMap,
Marker,
InfoWindow,
} from "react-google-maps";
let locationsData;
fetch("http:///locations")
.then(function (u) {
return u.json();
})
.then(function (json) {
locationsData = json;
});
function Map() {
const [selectedLoc, setSelectedLoc] = useState(null);
return (
<div className="App">
<GoogleMap
defaultZoom={14}
defaultCenter={{ lat: 49.55848, lng: 23.312481 }}
>
{locationsData.map((loc) => (
<Marker
key={loc._id}
position={{
lat: loc.gps[0],
lng: loc.gps[1],
}}
onClick={() => {
setSelectedLoc(loc);
}}
/>
))}
{selectedLoc && (
<InfoWindow
position={{
lat: selectedLoc.gps[0],
lng: selectedLoc.gps[1],
}}
onCloseClick={() => {
setSelectedLoc(null);
}}
>
<div>
<h2>{selectedLoc.name}</h2>
</div>
</InfoWindow>
)}
</GoogleMap>
</div>
);
}
const WrappedMap = withScriptjs(withGoogleMap(Map));
export default function App() {
return (
<div style={{ width: "100vw", height: "100vh" }}>
<WrappedMap
googleMapURL={`https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places&key=000`}
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `100%` }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
</div>
);
}
Server data example:
[
{
"gps": [11, 11],
"_id": "1",
"name": "Name1"
},
{
"gps": [22, 22],
"_id": "2",
"name": "Name2"
}
]
You will need to use useEffect and do the fetching in there. You will also have to store the data in the state so that the component will re-render once the data are fetched.
function Map() {
const [selectedLoc, setSelectedLoc] = useState(null);
const [locationsData, setLocationsData] = useState([]);
useEffect(() => {
fetch("http:///locations")
.then(function(u) {
return u.json();
})
.then(function(json) {
setLocationsData(json);
});
}, [])
return (
<div className="App">
<GoogleMap
defaultZoom={14}
defaultCenter={{ lat: 49.55848, lng: 23.312481 }}
>
{locationsData.map((loc) => (
<Marker
key={loc._id}
position={{
lat: loc.gps[0],
lng: loc.gps[1],
}}
onClick={() => {
setSelectedLoc(loc);
}}
/>
))}
{selectedLoc && (
<InfoWindow
position={{
lat: selectedLoc.gps[0],
lng: selectedLoc.gps[1],
}}
onCloseClick={() => {
setSelectedLoc(null);
}}
>
<div>
<h2>{selectedLoc.name}</h2>
</div>
</InfoWindow>
)}
</GoogleMap>
</div>
);
}
before it gets assigned (locationsData = json;), the locationsData is undefined as you stated here in the initial declaration: let locationsData;
so undefined.map() will throw you the error, you can do this to remain a consistent behavior:
let locationsData = [];
or you can have a validation before calling the .map() function:
{Array.isArray(locationsData) && locationsData.map((loc) => (
<Marker
key={loc._id}
position={{
lat: loc.gps[0],
lng: loc.gps[1],
}}
onClick={() => {
setSelectedLoc(loc);
}}
/>
))}
As #Gabriele pointed out, the fetch operation is placed outside of the component. You can do this to ensure the results get assigned correctly and the component will render it:
function Map() {
const [selectedLoc, setSelectedLoc] = useState(null);
const [locationsData, setLocationsData] = useState([]);
useEffect(() => {
fetch("http:///locations")
.then(u => u.json())
.then(json => setLocationsData(json));
}, []); // Empty dependency, acts similar to componentDidMount
return (
<div className="App">
<GoogleMap
defaultZoom={14}
defaultCenter={{ lat: 49.55848, lng: 23.312481 }}
>
{locationsData.map((loc) => (
<Marker
key={loc._id}
position={{
lat: loc.gps[0],
lng: loc.gps[1],
}}
onClick={() => {
setSelectedLoc(loc);
}}
/>
))}
{selectedLoc && (
<InfoWindow
position={{
lat: selectedLoc.gps[0],
lng: selectedLoc.gps[1],
}}
onCloseClick={() => {
setSelectedLoc(null);
}}
>
<div>
<h2>{selectedLoc.name}</h2>
</div>
</InfoWindow>
)}
</GoogleMap>
</div>
);
}
Initialize locationData with empty array..
let locationsData= [];

Google Maps API - How to center marker and map

I have played around with the geolocation of javascript. So I created a react component, which shows your current location. I want to visualize this with Google Maps. It works fine so far, but however, if the current position moves outside the start map, the map does not pan. My goal is that the marker is always centered, and the map scrolls.
This is what I have so far. The current position is being passed to the component via the props props.latitude and props.longitude.
UPDATE
Now I know why this is not working.
window.google.maps.Map(document.getElementsByClassName("google-map"))
is not working. It does not find the map this way. So it cannot update its properties.
Anybody an idea how to access the current map?
Here is the code:
import React, {useState,useRef,useCallback} from 'react';
import { compose, withProps } from "recompose"
import { withScriptjs, withGoogleMap, GoogleMap, Marker } from "react-google-maps"
function Map(props) {
const [center, setCenter] = useState({ lat: props.latitude, lng: props.longitude });
const refMap = useRef(null);
const handlePositionChanged = () => {
let position = new window.google.maps.LatLng(parseInt(props.latitude), parseInt(props.longitude));
window.google.maps.Marker(document.getElementsByClassName("google-map-marker")).setPosition(position);
window.google.maps.Map(document.getElementsByClassName("google-map")).setCenter(position);
};
return (
<GoogleMap
className="google-map"
ref={refMap}
defaultZoom={19}
mapTypeId='satellite'
defaultCenter={{ lat: props.latitude, lng: props.longitude }}
onBoundsChanged={useCallback(handlePositionChanged)}
>
<Marker className="google-map-marker" position={{ lat: props.latitude, lng: props.longitude }} position_changed={useCallback(handlePositionChanged)} />
</GoogleMap>
);
}
export default compose(
withProps({
googleMapURL: "https://maps.googleapis.com/maps/api/js?key=AIz&v=3.exp&libraries=geometry,drawing,places",
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `500px` }} />,
mapElement: <div style={{ height: `100%` }} />,
}),
withScriptjs,
withGoogleMap
)((props) => <Map latitude={props.latitude} longitude={props.longitude} />);
Instead of setting the center of the map and the marker using window.google.maps., you can use state values to change the <GoogleMap/> center parameter and the <Marker/> position parameter. So every time there's a change in the coordinates from geolocation, you need to change the state value of the Map's center and Marker's position and it the changes will then be reflected to the map. Here's a sample code snippet:
import React, { Component } from 'react';
import { withGoogleMap, GoogleMap, Marker } from 'react-google-maps';
class Map extends Component {
constructor(props) {
super(props);
this.state = {
userPosition: { lat: 40.756795, lng: -73.954298 }
};
}
onMapLoad = () => {
navigator.geolocation.getCurrentPosition(position => {
const { latitude, longitude } = position.coords;
this.setState({
userPosition: { lat: latitude, lng: longitude }
});
});
};
render() {
const GoogleMapExample = withGoogleMap(props => (
<GoogleMap
center={this.state.userPosition}
defaultZoom={13}
onLoad={this.onMapLoad()}
>
<Marker position={this.state.userPosition} />
</GoogleMap>
));
return (
<div>
<GoogleMapExample
containerElement={<div style={{ height: `500px`, width: '500px' }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
</div>
);
}
}
export default Map;

react-google-maps MapWithAMarker fails to rerender when marker position changes

I took the react-google-maps MapWithAMarker example :
import React from 'react';
import { compose, withProps, lifecycle} from 'recompose';
import {
withScriptjs,
withGoogleMap,
GoogleMap,
Marker,
} from 'react-google-maps';
import keys from '../../components/config/keys';
const MapWithAMarker = compose(
withProps({
googleMapURL: `https://maps.googleapis.com/maps/api/js?key=${keys.google_maps_api_key}&v=3.exp&libraries=geometry,drawing,places`,
loadingElement: <div style={{ height: '100%' }} />,
containerElement: <div style={{ height: '400px' }} />,
mapElement: <div style={{ height: '100%' }} />,
}),
withScriptjs,
withGoogleMap
)(props =>
<GoogleMap
defaultZoom={8}
defaultCenter={{ lat: props.lat, lng: props.lng }}>
<Marker position={{ lat: props.lat, lng: props.lng }}/>
</GoogleMap>
);
export default MapWithAMarker;
and I use it like this:
<MapWithAMarker lat={parseFloat(excursion.lat)}
lng={parseFloat(excursion.lng)} />
I use React router in my application.
Observed behaviour :
When i render the page for the first time, the map renders correctly.
If i click on a link to rerender the page with a different marker position, the map fails to load and just renders a empty grey rectangle with only "press ctrl plus scroll to zoom" when i scroll on it.
Solved :
I needed to change the defaultCenter to center and defaultZoom to zoom :
<GoogleMap zoom={8}
center={{ lat: props.lat, lng: props.lng }}>
<Marker position={{ lat: props.lat, lng: props.lng }}/>
</GoogleMap>

When displaying multiple markers on a map, how to open just one info window, when clicking on a marker?

I'm using react-google-maps to display a map with markers, and when you click on a marker, all the info windows open up. I would like to display only one marker's info window when I click on it, and for the others to stay closed.
Here is my code:
<GoogleMap
defaultZoom={15}
defaultCenter={{ lat: 51.508530, lng: -0.076132 }}
>
{props.places && props.places.map((place, i) =>
<Marker onClick={props.onToggleOpen} key={i} position={{ lat: place.geometry.location.lat(), lng: place.geometry.location.lng() }} >
{props.isOpen && <InfoWindow onCloseClick={props.onToggleOpen}>
<div>{place.name}</div>
</InfoWindow>}
</Marker>
)}
</GoogleMap>
And I'm opening and closing the InfoWindow with this
import { compose, withProps, withStateHandlers, withHandlers, withState } from "recompose";
...
withStateHandlers(() => ({
isOpen: false,
}), {
onToggleOpen: ({ isOpen, id }) => () => ({
isOpen: !isOpen,
})
}),
I'm mapping over all the markers, and displaying them on the map. How could I click open just one marker InfoWindow?
Here is a related question, but it's not made with React, and doesn't use the react-google-maps.
It's more of a React question. You can pass the index of a clicked Marker to onToggleOpen and instead of isOpen you use a selectedPlace state that holds the index of a clicked Marker and use this index to render the right InfoWindow.
Here is an example (not fully tested, but you can get the idea):
/*global google*/
import React from "react"
import { compose, withProps, withHandlers, withState, withStateHandlers } from "recompose"
import { withScriptjs, withGoogleMap, GoogleMap, Marker, InfoWindow } from "react-google-maps"
const MyMapComponent = compose(
withProps({
googleMapURL: "https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places",
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `400px` }} />,
mapElement: <div style={{ height: `100%` }} />,
}),
withScriptjs,
withGoogleMap,
withState('places', 'updatePlaces', ''),
withState('selectedPlace', 'updateSelectedPlace', null),
withHandlers(() => {
const refs = {
map: undefined,
}
return {
onMapMounted: () => ref => {
refs.map = ref
},
fetchPlaces: ({ updatePlaces }) => {
let places;
const bounds = refs.map.getBounds();
const service = new google.maps.places.PlacesService(refs.map.context.__SECRET_MAP_DO_NOT_USE_OR_YOU_WILL_BE_FIRED);
const request = {
bounds: bounds,
type: ['hotel']
};
service.nearbySearch(request, (results, status) => {
if (status == google.maps.places.PlacesServiceStatus.OK) {
console.log(results);
updatePlaces(results);
}
})
},
onToggleOpen: ({ updateSelectedPlace }) => key => {
updateSelectedPlace(key);
}
}
}),
)((props) => {
console.log(props);
return (
<GoogleMap
onTilesLoaded={props.fetchPlaces}
ref={props.onMapMounted}
onBoundsChanged={props.fetchPlaces}
defaultZoom={15}
defaultCenter={{ lat: 51.508530, lng: -0.076132 }}
>
{props.places && props.places.map((place, i) =>
<Marker onClick={() => props.onToggleOpen(i)} key={i} position={{ lat: place.geometry.location.lat(), lng: place.geometry.location.lng() }}>
{props.selectedPlace === i && <InfoWindow onCloseClick={props.onToggleOpen}>
<div>
{props.places[props.selectedPlace].name}
</div>
</InfoWindow>}
</Marker>
)}
</GoogleMap>
)
})
export default class MyFancyComponent extends React.PureComponent {
render() {
return (
<MyMapComponent />
)
}
}

Categories