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

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= [];

Related

React Google Maps unique key error in loop

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>

Don't want to re-render even if the state is changed in react-google-maps

I am using an application that displays a Google map with react Google Maps have multiple pins installed, and the state changes by scrolling, and the active flight changes according to the state.
At that time, the center of the Google map is set to be an activity, but the Google map is re-rendered when the state changes. I don't know how to prevent rendering.
  Google Maps has NPM library. It uses react-google-maps and is implemented using hooks. I tried to return false with useEffect (), but I didn't hear it as it is. Please tell me
MapComponent(HOC)
import React from "react";
import { withGoogleMap, GoogleMap, withScriptjs, Marker, InfoWindow } from "react-google-maps";
import { compose, withProps, withHandlers, withStateHandlers } from "recompose";
const MapWithPlaces = compose(
withProps({
googleMapURL:
`https://maps.googleapis.com/maps/api/js?key=${process.env.REACT_APP_GOOGLE_PLACE_API_KEY}&libraries=geometry,drawing,places`,
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: "400px", width: "100%" }} />,
mapElement: <div style={{ height: "100%" }} />
}),
withStateHandlers(
props => ({
infoWindows: props.places.map(p => {
return { isOpen: false };
}),
defaultCenter: { 'lat': props.lat, 'lng': props.lng }
}),
{
onToggleOpen: ({ infoWindows }) => selectedIndex => ({
infoWindows: infoWindows.map((iw, i) => {
iw.isOpen = selectedIndex === i;
return iw;
})
})
}
),
withHandlers(() => {
const refs = {
map: undefined,
}
console.log(refs);
return {
onMapMounted: () => ref => {
refs.map = ref
},
onZoomChanged: ({ onZoomChange }) => (props) => {
const center = { 'lat': parseFloat(props.lat, 10), 'lng': parseFloat(props.lng, 10) }
refs.map.pantTo(center)
}
}
}),
withScriptjs,
withGoogleMap
)(props => (
<GoogleMap defaultZoom={props.zoom} defaultCenter={props.center} key={props.key} ref={map}>
{props.places &&
props.places.map((place, i) => {
let lat = parseFloat(place.lat, 10);
let lng = parseFloat(place.lng, 10);
return (
<Marker
id={place.id}
key={place.key}
position={{ lat: lat, lng: lng }}
title={place.name}
onClick={props.onToggleOpen.bind(this, i)}
opacity={place.key === props.step ? 1 : 0.5}
label={place.day === props.currentDay ? place.dayIndex.toString() : ''}
>
{props.infoWindows[i].isOpen && (
<InfoWindow onCloseClick={props.onToggleOpen.bind(i)}>
<div>{place.name}</div>
</InfoWindow>
)}
</Marker>
);
})}
</GoogleMap>
));
export default MapWithPlaces;
MapComponent(hooks)
import React, { useState, useEffect, useRef } from "react";
import { withGoogleMap, withScriptjs, GoogleMap, Marker, InfoWindow } from "react-google-maps";
// import mapStyles from "./mapStyles";
const MapCreate = React.memo((props) => {
// const [selectedPark, setSelectedPark] = useState(null);
const mapRef = useRef()
useEffect(() => {
console.log("props updates")
console.log(props);
const mapCenter = {
lat: parseFloat(props.places[props.step].lat, 10),
lng: parseFloat(props.places[props.step].lng, 10)
}
return false
// refMap.current.panTo(mapCenter) //move the map to new location
}, [props]);
return (
<GoogleMap defaultZoom={14} center={{ lat: props.center.lat, lng: props.center.lng }} ref={mapRef}>
{props.places && props.places.map((place, i) => {
let lat = parseFloat(place.lat, 10);
let lng = parseFloat(place.lng, 10);
return (
<Marker
id={place.id}
key={place.key}
position={{ lat: lat, lng: lng }}
title={place.name}
opacity={place.key === props.step ? 1 : 0.5}
label={place.day === props.currentDay ? place.dayIndex.toString() : ''}
>
</Marker>
)
})}
</GoogleMap>
)
})
const MapWrapped = withScriptjs(withGoogleMap(MapCreate));
export default function Map(props) {
const mapRef = useRef(null)
return (
<div style={{ width: "100%", height: "400px" }}>
<MapWrapped
googleMapURL={`https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places&key=${process.env.REACT_APP_GOOGLE_PLACE_API_KEY}`}
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: "400px", width: "100%" }} />}
mapElement={<div style={{ height: `100%` }} />}
{...props}
/>
</div>
);
}
Try #shouldcomponentupdate
shouldComponentUpdate(nextProps, nextState)
Use shouldComponentUpdate() to let React know if a component’s output
is not affected by the current change in state or props. The default
behavior is to re-render on every state change, and in the vast
majority of cases you should rely on the default behavior.
shouldComponentUpdate() is invoked before rendering when new props or
state are being received. Defaults to true. This method is not called
for the initial render or when forceUpdate() is used

add a route, google is unedfined

I try to add a simple route in my map but when i call google.maps.DirectionsService() i get this google is undefined, so i try to add a const variable but now the error message is Cannot read property 'maps' of undefined..
I don't know what im doing wrong
const google = window.google;
class Map extends Component {
constructor(props) {
super(props);
this.state = {
latitude: 0,
longitude: 0,
nombre_de_bars: null
}
}
map() {
const [selectedBar, setSelectedBar] = useState(null);
const { nombre_de_bars } = this.state
const rows = nombre_de_bars.map(bar =>
<Marker key={bar._id}
position={{
lat: bar.latitude,
lng: bar.longitude
}}
onClick={() => {
setSelectedBar(bar);
}}
icon={{
url: '/images/biere_logo.png',
scaledSize: new window.google.maps.Size(25, 25)
}}
/>
);
return (
<GoogleMap
defaultZoom={14}
defaultCenter={{ lat: this.state.latitude, lng: this.state.longitude }}
defaultOptions={{
zoomControl: false,
mapTypeControl: false,
scaleControl: false,
streetViewControl: false,
rotateControl: false,
fullscreenControl: false
}}>
<Marker
position={{ lat: this.state.latitude, lng: this.state.longitude }}
icon={{
url: '/images/marker.png',
scaledSize: new window.google.maps.Size(50, 50)
}}
/>
{rows}
{selectedBar && (
<InfoWindow
position={{
lat: selectedBar.latitude,
lng: selectedBar.longitude
}}
onCloseClick={() => {
setSelectedBar(null);
}}
>
<div>
<p>{selectedBar.name}</p>
<p>{selectedBar.description}</p>
<button onClick={() => {
this.itineraireTo(selectedBar);
}}>Je m'y rend</button>
</div>
</InfoWindow>
)}
</GoogleMap>
);
}
render() {
const WrappedMap = withScriptjs(withGoogleMap(this.map.bind(this)));
const DirectionsService = new google.maps.DirectionsService();
return (
<WrappedMap
googleMapURL="https://maps.googleapis.com/maps/api/js?key=MY_KEY&v=3.exp&libraries=geometry,drawing,places"
loadingElement={<div style={{ height: `100%`, width: '100%' }} />}
containerElement={<div style={{ height: `100%`, width: '100%' }} />}
mapElement={<div style={{ height: `100%`, width: '100%' }} />}
/>
);
}
}
export default Map;
I try to export with withGoogleMap(Map) but not fix the problem.
I read the documentation on tomchentw.github.io but not the similar problem was occured

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 />
)
}
}

Pass back coordinates to display in react-google-maps

Below is my code:
const SiteGoogleMap = compose(
withProps({
googleMapURL: `https://maps.googleapis.com/maps/api/js?key=${GOOGLE_MAP_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={18}
defaultCenter={{ lat: 3.1314067, lng: 101.6285082 }}
>
{props.isMarkerShown && <Marker position={{ lat: -34.397, lng: 150.644
}} onClick={props.onMarkerClick} />}
<DrawingManager
defaultDrawingMode={google.maps.drawing.OverlayType.POLYGON}
defaultOptions={{
drawingControl: true,
drawingControlOptions: {
position: google.maps.ControlPosition.TOP_CENTER,
drawingModes: [
google.maps.drawing.OverlayType.POLYGON
],
}
}}
/>
<Polygon path={coordinates} />
</GoogleMap>
)
I would like to get the coordinates (polygon path) from my server. Below is what I have done to get it. It is a component:
export default class Map extends Component {
state = {
isMarkerShown: false,
coordinates: []
}
componentDidMount() {
this.fetchBoundaries()
}
render () {
const { coordinates } = this.state
return (
<SiteGoogleMap
isMarkerShown={this.state.isMarkerShown}
onMarkerClick={this.handleMarkerClick}
/>
);
}
async fetchBoundaries () {
try {
const { coordinates } = this.state
const item = await service.getBoundaries('59d850878328bd177bf50b4d')
coordinates = item.boundaries
this.setState({ coordinates })
} catch (e) {
notification.show('error', 'Unable to load successfully', 'Unable to load client successfully. Please try again later.')
}
}
}
From the fetchBoundaries() function, I am able to get my coordinates. But, it just can't pass it to <Polygon path={coordinates} /> for display.
Anyone know what went wrong?

Categories