React Google Maps InfoWindow toggle display one at a time - javascript

I am currently using react-google-maps and I am able to display the marker and InfoWindow correctly by clicking on the marker (from the map or from the list). But once I click on a marker to open, the previous InfoWindow stays open even when I select a new one.
I am able to close the InfoWindow if I click on the 'x' or marker from list. But I'm not sure how to close other InfoWindows automatically and only open the current marker's InfoWindow.
I have looked at several similar questions to get an idea: When displaying multiple markers on a map, how to open just one info window, when clicking on a marker?, React-Google-Map multiple Info window open
.
But I'm still having trouble with passing and checking for unique marker so that only the InfoWindow which matches the marker ID is displayed.
class MarkerInfoList extends Component {
constructor(props){
super(props);
this.state = {
isOpen: false
};
}
handleToggleOpen = (markerId) => {
this.setState({
isOpen: true
});
}
handleToggleClose = (markerId) => {
this.setState({
isOpen: false
});
}
render() {
const isOpen = this.state.isOpen;
return (
<div>
{this.state.isOpen ? (
<ul>
<li onClick={() => this.handleToggleClose()}>{this.props.venue}</li>
<Marker
key={this.props.index}
id={this.props.index}
position={{ lat: this.props.lat, lng: this.props.lng}}
defaultAnimation={google.maps.Animation.DROP}
onClick={() => this.handleToggleOpen(this.props.index)}>
<InfoWindow
id = {this.props.index}
onCloseClick={() => this.setState({isOpen: false})}>
<div key={this.props.index}>
<h4>{this.props.venue}</h4>
</div>
</InfoWindow>
</Marker>
</ul>
) : (
<ul>
<li onClick={() => this.handleToggleOpen()}>{this.props.venue}</li>
</ul>
)
}
</div>
)
}
}
export default MarkerInfoList;

I wanted to toggle individual windows but also needed to know if the locations were "active" on a parent component. So in the parent component I have state the with an active key. I am passing that down to the Map as activeKey component as well as the initial list with all the location data as locations.
<GoogleMap defaultZoom={16} defaultCenter={this.state.currentLocation}>
{this.props.locations.map((location, i) => (
<Marker
key={location.key}
position={{
lat: location.latitude,
lng: location.longitude
}}
onClick={() => {
this.props.toggleLocationsActive(location.key);
// this.setCurrentLocation(location.latitude, location.longitude);
}}
>
{location.key === this.props.activeKey && (
<InfoWindow onCloseClick={props.onToggleOpen}>
<div>
{location.orgName}
{location.orgName !== location.programName &&
"/" + location.programName}
</div>
</InfoWindow>
)}
</Marker>
))}
</GoogleMap>
In the parent component I had state = {activeKey: ""} and the following method:
toggleLocationsActive = locationKey => {
this.setState({
activeKey: locationKey
});
};
Both are are passed down in props:
<Map
activeKey={this.state.activeKey}
toggleLocationsActive={this.toggleLocationsActive}
/>
Please note that I if you don't require this state to exist in the parent component you could do this within the map component.

Related

How to add "outside" event listener to Markers in Google Maps (react-google-maps/api)

I have a list of hikes stored in state and I rendered the location of those hikes as Markers on the Google Map Component like so:
{hikes.map(hike =>
<Marker
position={{lat: hike.coordinates.latitude, lng: hike.coordinates.longitude}}
icon = {
{ url:"https://static.thenounproject.com/png/29961-200.png",
scaledSize : new google.maps.Size(50,50)
}
}
onClick={()=>{console.log(hike.name)}}
/>
I also display the list of hikes and its other details in its own BusinessCard Component like so:
export const Businesses = (props)=>{
const {hikes, addToTrip} = props
return(<>
<div className="businessesColumn">
{hikes.map(hike=>(
<BusinessCard.../>
))}
When I hover over each of the BusinessCard components, I want the corresponding marker to animate "bounce." I tried manipulate google.maps.event.addListener but I think I was doing it wrong. I'm not sure if it can detect events outside of the GoogleMap component? Any ideas on how should I approach this problem?
Okay so I don't know exactly how your components are structured, but try adding state activeMarker inside the component where your Markers are located. Then pass down setActiveMarker as a prop to the Business component. And inside the Business component, pass down hike.coordinates.latitude, hike.coordinates.longitude and setActiveMarker as props to the BusinessCard components. Inside BusinessCard, add onHover={() => props.setActiveMarker({ lat: props.latitude, lng: props.longitude })}
Something like this...
Component where Markers are located
const [activeMarker, setActiveMarker] = useState(null)
return (
<>
<GoogleMap>
{hikes.map(hike => (
<Marker
position={{lat: hike.coordinates.latitude, lng: hike.coordinates.longitude}}
icon = {{
url:"https://static.thenounproject.com/png/29961-200.png",
scaledSize : new google.maps.Size(50,50)
}}
animation={
(hike.coordinates.latitude === activeMarker.lat
&& hike.coordinates.longitude === activeMarker.lng)
? google.maps.Animation.BOUNCE : undefined
}
/>
))}
</GoogleMap>
<Business setActiveMarker={setActiveMarker} />
</>
)
Business component
return(
<div className="businessesColumn">
{hikes.map(hike => (
<BusinessCard
latitude={hike.coordinates.latitude}
longitude={hike.coordinates.longitude}
setActiveMarker={props.setActiveMarker}
/>
))}
</div>
)
BusinessCard component
return (
<div
className="business-card"
onMouseEnter={() => props.setActiveMarker({ lat: props.latitude, lng: props.longitude })}
>
{/* Whatever */}
</div>
)

google-maps-react centers me in the middle of the ocean when given new bounds

I am using the google-maps-react package and I am trying to have bounds for my map. They should change based on values passed to the component through props. It does successfully do this, but after the first load of my website instead of reloading the map so that I can see all the markers, it just puts me at lat: 0.0 and long: 0.0. If I zoom out I can see all of my markers, but the map does not resize itself.
It does successful load! Just doesn't load the bounds. See the images below.
After I save my file and it reloads
After I leave the page and come back, or refresh
Here is what I've tried:
import { Map, Marker, GoogleApiWrapper } from "google-maps-react";
class MapExample extends Component {
constructor(props) {
super(props);
this.state = {
bounds: null
};
this.handleMapLoad = this.handleMapLoad.bind(this);
}
handleMapLoad() {
const bounds = new this.props.google.maps.LatLngBounds();
for (let loc of this.props.originsCoords) bounds.extend({ lat: loc.lat, lng: loc.lng });
this.setState({bounds:bounds});
}
render() {
return (
<Map
onReady={this.handleMapLoad}
google={this.props.google}
bounds={this.state.bounds}
style={{width:'50%', height:'50%'}}
>
{this.props.originsCoords.map((loc, i) => (
<Marker key={i} position={{ lat: loc.lat, lng: loc.lng }} />
))}
</Map>
);
}
}
export default GoogleApiWrapper({
apiKey: "api-key"
})(MapExample);

onCenterChange callback returns undefined #react-google-maps/api

I have been using a library called #react-google-maps/api and I want to store the map center as react state and I want the user to be able to drag the map while the marker always stays at the center of the map (uber style location selection)
The Problem is when I call the onCenterChange of the component, it returns me undefined
and when after store the map instance (recieved on the onLoad callback) as react state. The map instance returns the exact same center everytime (I guess the state save is static)
<GoogleMap
id={id}
zoom={zoom}
center={center}
options={options}
mapContainerStyle={{ width, height }}
onLoad={m => {
if (!map) setMap(m);
}}
onCenterChanged={e => {
console.log(map);
if (map) {
console.log(parseCoords(map.getCenter()));
}
}}
>
{children}
</GoogleMap>
Indeed, onCenterChanged event does not accept any arguments in #react-google-maps/api:
onCenterChanged?: () => void;
Instead map instance could be retrieved via onLoad event handler and map center saved in state like this:
function Map(props) {
const mapRef = useRef(null);
const [position, setPosition] = useState({
lat: -25.0270548,
lng: 115.1824598
});
function handleLoad(map) {
mapRef.current = map;
}
function handleCenterChanged() {
if (!mapRef.current) return;
const newPos = mapRef.current.getCenter().toJSON();
setPosition(newPos);
}
return (
<GoogleMap
onLoad={handleLoad}
onCenterChanged={handleCenterChanged}
zoom={props.zoom}
center={props.center}
id="map"
>
</GoogleMap>
);
}
Here is a demo which demonstrates how to keep the marker centered of the map while dragging the map.

Set marker timeout in React

The animation prop in this Marker component rendering gets a marker to bounce if there's one and drop if there are multiple, using the marker's array:
<Marker
key={index}
position={{lat: marker.lat, lng: marker.lng}}
onClick={() => props.handleMarkerClick(marker)}
animation={array.length === 1 ? google.maps.Animation.BOUNCE : google.maps.Animation.DROP}
>
However, my goal is to set a 750ms timeout for the bounce (based on an answer to another SO question). I can't do this with the conditional ternary operator, so I made two attempts at creating a function for this, replacing the animation property in the Marker component with animation={array.length === 1 ? bounceMarker() : google.maps.Animation.DROP} and both had issues.
This one I thought would work, but using google.maps.Animation.BOUNCE; throws an ESLint error "Expected an assignment or function call and instead saw an expression."
const bounceMarker = () => {
google.maps.Animation.BOUNCE;
setTimeout(() => {
marker.setAnimation(null);
}, 750);
};
This function is based on Google's markers tutorial, and caused the error "marker.getAnimation is not a function":
const bounceMarker = () => {
if (marker.getAnimation() !== null) {
marker.setAnimation(null);
} else {
marker.setAnimation(google.maps.Animation.BOUNCE);
setTimeout(() => {
marker.setAnimation(null);
}, 750);
}
};
In case it's useful here's my full code from this file, with the two functions commented out:
// Specifies global variable to ESLint (bundled with create-react-app), circumventing no-undef rule. See https://eslint.org/docs/user-guide/configuring#specifying-globals
/* global google */
// This component's code is from react-google-maps implementation instructions https://tomchentw.github.io/react-google-maps/#installation
import React, { Component , Fragment } from 'react';
import { withScriptjs , withGoogleMap, GoogleMap, Marker, InfoWindow } from 'react-google-maps';
const MyMapComponent = withScriptjs(
withGoogleMap(props => (
<GoogleMap
zoom={props.zoom}
defaultCenter={{ lat: 47.6093, lng: -122.3309 }}
>
{/* If there are markers, filters all visible markers (creating new array) then maps over newly created array taking the marker and marker's array index as arguments, rendering each Marker component with the marker index set as the key and the marker's lat and long as the position */}
{props.markers &&
props.markers.filter(marker => marker.isVisible)
// "Keys help React identify which items have changed, are added, or are removed. Keys should be given to the elements inside the array to give the elements a stable identity ... When you don’t have stable IDs for rendered items, you may use the item index as a key as a last resort" https://reactjs.org/docs/lists-and-keys.html
.map((marker, index, array) => {
// const bounceMarker = () => {
// if (marker.getAnimation() !== null) {
// marker.setAnimation(null);
// } else {
// marker.setAnimation(google.maps.Animation.BOUNCE);
// setTimeout(() => {
// marker.setAnimation(null);
// }, 750);
// }
// };
// const bounceMarker = () => {
// google.maps.Animation.BOUNCE;
// setTimeout(() => {
// marker.setAnimation(null);
// }, 750);
// };
const venueInfo = props.venues.find(venue => venue.id === marker.id);
return (
<Marker
key={index}
position={{lat: marker.lat, lng: marker.lng}}
// Marker click event listener, defined in App component class
onClick={() => props.handleMarkerClick(marker)}
animation={array.length === 1 ? bounceMarker() : google.maps.Animation.DROP}
>
{/* Show marker's InfoWindow when its isOpen state is set to true (set in app.js) */}
{marker.isOpen &&
<InfoWindow>
{/* If a venueInfo is not falsey and: 1) if there's a name and bestPhoto property, return the venue photo and name; 2) if there's only a name property, display the name only; 3) if there's only a photo property, display the photo only. If neither are available and/or venueInfo is falsy display text indicating no info available. See SO question about multiple ternary operators https://stackoverflow.com/questions/7757549/multiple-ternary-operators */}
{venueInfo && venueInfo.name && venueInfo.bestPhoto ?
<Fragment>
<p>{venueInfo.name}</p>
<img src={`${venueInfo.bestPhoto.prefix}200x200${venueInfo.bestPhoto.suffix}`}
// Screen readers already announce as image; don't need the word "image", "photo", etc.
alt={"Venue"}
/>
</Fragment> : venueInfo && venueInfo.name ? <p>{venueInfo.name}</p> : venueInfo && venueInfo.bestPhoto ? <img src={`${venueInfo.bestPhoto.prefix}200x200${venueInfo.bestPhoto.suffix}`}
// Screen readers already announce as image; don't need the word "image", "photo", etc.
alt={"Venue"}
/> : <p>No info available</p>}
</InfoWindow>
}
</Marker>
);
})}
</GoogleMap>
))
);
export default class Map extends Component {
render() {
return (
<MyMapComponent
// This is making the this.setState passed into Map component (as its prop) inside App's component class's render method available to MyMapComponent, which is how props from this.setState are eventually included inside MyMapComponent class (such as zoom={props.zoom})
{...this.props}
isMarkerShown
googleMapURL="https://maps.googleapis.com/maps/api/js?v=3.exp&key=AIzaSyDjl8LxY7Edfulq6t_VDaQsYY4ymPjwN0w"
// CSS declarations are placed in double curly braces because attributes accept JS objects; this is how to include an object literal. See https://stackoverflow.com/questions/22671582/what-is-the-purpose-of-double-curly-braces-in-reacts-jsx-syntax
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `100%`, width: `75%` }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
);
}
}
You are getting this error
marker.getAnimation is not a function
since marker is not the actual google.maps.Marker object.
Instead of rendering markers and afterwards updating Marker.animation property, how about to render the marker with a timeout (basically the same way as it is demonstrated in Marker Animations With setTimeout() example)?
For that purpose it is proposed to introduce a state for keeping animated markers
class MarkersAnimation extends React.Component {
constructor(props) {
super(props);
this.state = {
markers: []
};
}
render() {
return (
<GoogleMap
defaultZoom={this.props.zoom}
defaultCenter={this.props.center}
>
{this.state.markers.map((place, i) => {
return (
<Marker
animation={google.maps.Animation.BOUNCE}
id={place.id}
key={place.id}
position={{ lat: place.lat, lng: place.lng }}
/>
);
})}
</GoogleMap>
);
}
componentDidMount() {
const { markers, delay } = this.props;
this.interval = setInterval(() => {
this.setState(prev => ({
markers: prev.markers.concat([markers.shift()])
}));
if (!markers.length) clearInterval(this.interval);
}, delay);
}
componentWillUnmount() {
clearInterval(this.interval);
}
}
Here is a demo

Dynamically Adding Markers on google-map-react

What I wan't to do is to show the location picked from some mobile devices on the Map.
Data about the locations are there..
What I need here is to add Markers on the map depending on the data received from the server.
Assume I have set the location data ({Lat,Lang}) to the state markers
Then How can I add this to show in Map.
My Map Code is as follows!
import React, { Component } from 'react';
import GoogleMapReact from 'google-map-react';
const AnyReactComponent = ({ text }) => <div>{text}</div>;
class MyClass extends Component {
constructor(props){
super(props);
}
render() {
return (
<GoogleMapReact
defaultCenter={this.props.center}
defaultZoom={this.props.zoom}
style={{height: '300px'}}
>
<AnyReactComponent
lat={59.955413}
lng={30.337844}
text={'Google Map'}
/>
</GoogleMapReact>
);
}
}
MyClass.defaultProps = {
center: {lat: 59.95, lng: 30.33},
zoom: 11
};
export default MyClass;
This Code is from the answer Implementing google maps with react
Used npm package :- google-map-react
You may try:
import React, { Component } from 'react';
import GoogleMapReact from 'google-map-react';
const AnyReactComponent = ({ img_src }) => <div><img src={img_src} className="YOUR-CLASS-NAME" style={{}} /></div>;
class MyClass extends Component {
constructor(props){
super(props);
this.state = {
markers: [],
}
}
componentDidMount(){
// or you can set markers list somewhere else
// please also set your correct lat & lng
// you may only use 1 image for all markers, if then, remove the img_src attribute ^^
this.setState({
markers: [{lat: xxxx, lng: xxxx, img_src: 'YOUR-IMG-SRC'},{lat: xxxx, lng: xxxx, img_src: 'YOUR-IMG-SRC' },{lat: xxxx, lng: xxxx, img_src: 'YOUR-IMG-SRC'}],
});
}
render() {
return (
<GoogleMapReact
defaultCenter={this.props.center}
defaultZoom={this.props.zoom}
style={{height: '300px'}}
>
{this.state.markers.map((marker, i) =>{
return(
<AnyReactComponent
lat={marker.lat}
lng={marker.lng}
img_src={marker.img_src}
/>
)
})}
</GoogleMapReact>
);
}
}
MyClass.defaultProps = {
center: {lat: 59.95, lng: 30.33},
zoom: 11
};
If this has error, please show here too, then we can fix it later
===========
ADDED EXAMPLE FOR CLICK-EVENT ON MARKERS
markerClicked(marker) {
console.log("The marker that was clicked is", marker);
// you may do many things with the "marker" object, please see more on tutorial of the library's author:
// https://github.com/istarkov/google-map-react/blob/master/API.md#onchildclick-func
// Look at their examples and you may have some ideas, you can also have the hover effect on markers, but it's a bit more complicated I think
}
render() {
return (
<GoogleMapReact
defaultCenter={this.props.center}
defaultZoom={this.props.zoom}
style={{height: '300px'}}
>
{this.state.markers.map((marker, i) =>{
return(
<AnyReactComponent
lat={marker.lat}
lng={marker.lng}
img_src={marker.img_src}
onChildClick={this.markerClicked.bind(this, marker)}
/>
)
})}
</GoogleMapReact>
);
}
Once again, post here some errors if any ^^ !
Be careful. You said react-google-map but you are using google-map-react. Those are 2 different packages. Do not mix up their documentation.

Categories

Resources