how to integrate google maps in react using package google-maps-react? - javascript

When I use google-maps-react to integrate maps, map loads correctly and even shows the correct markers but I am having some issues.
1- A styling issue normally map component overlays the content I think because it has position absolute but when I change its position to relative it does not show. I have placed it in a div everything above the div shows but it overlays the content below the div.
2- it is giving errors in the console (I am listing them below)
Using UNSAFE_componentWillReceiveProps in strict mode is not recommended and may indicate bugs in your code. Move data fetching code or side effects to componentDidUpdate.If you're updating state whenever props change, refactor your code to use memoization techniques or move it to static getDerivedStateFromProps.
Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the Wrapper component.
My mapComponent:
import React from "react";
import { Map, Marker, GoogleApiWrapper } from "google-maps-react";
export function MapContainer({ google, captains = [] }) {
const mapStyles = {
width: "85%",
height: "100%",
};
return (
<Map
google={google}
// containerStyle={{
// position: "absolute",
// width: "85%",
// height: "40%",
// }}
// style={mapStyles}
containerStyle={mapStyles}
initialCenter={{
lat: captains[0].location.coordinates[1],
lng: captains[0].location.coordinates[0],
}}
zoom={captains.length === 1 ? 18 : 13}
>
{captains.map((captain) => (
<Marker
key={captain._id}
position={{
lat: captain.location.coordinates[1],
lng: captain.location.coordinates[0],
}}
/>
))}
</Map>
);
}
export default GoogleApiWrapper({
apiKey: "#############################",
})(MapContainer);
Here is where I am using it:
import MapContainer from "../components/MapContainer";
function Captains({ captains }) {
return (
<div className=" h-screen overflow-auto">
<div className="pt-24 pl-20 flex flex-col">
<othercontent ..... />
<div className="visible h-1/3 w-10/12 mx-5">
<MapContainer captains={captains} />
</div>
<PaginationTable
.....
/>
</div>
</div>
</>
);
}

Related

React Google Maps Api doesn't render children elements at first render

I'm trying to use Google Maps Api in my app, everything is fine until I want to display a Marker at the first render of the map. The Marker is not showing up, but if I add one more Marker after the render is done, the Marker will appear.
So the problem is that I want to render the map with a Marker already there, I don't want to wait for some location to be selected.
I want to receive lat and lng from props, but for now I've made an hard coded const (center).
import React, { useMemo } from "react";
import { useJsApiLoader, GoogleMap, Marker } from "#react-google-maps/api";
export default function GoogleMaps({ lat, lng }) {
const { isLoaded } = useJsApiLoader({
googleMapsApiKey: process.env.REACT_APP_GOOGLE_MAPS_API_KEY,
});
const center = useMemo(() => ({ lat: 42.4332, lng: 20.4343 }), []);
if (!isLoaded) {
return <h2>Calculating Locations..</h2>;
}
return (
isLoaded && (
<GoogleMap
center={center}
zoom={17}
mapContainerStyle={{ width: "450px", height: "400px" }}
options={{ disableDefaultUI: true, mapId: "deleted for this snippet" }}
>
<Marker position={center} />
</GoogleMap>
)
);
}
Have you tried importing and using MarkerF instead of Marker?
See: https://github.com/JustFly1984/react-google-maps-api/issues/3048#issuecomment-1166410403
"MarkerF is functional component vs class based Marker component, which does not work with react Strict and/or react#17+"
Also, there are similar issues discussed here: Markers not rendering using #react-google-maps/api and here: Map Marker don't show up (Marker rendered before Map)- ReactJS with #react-google-maps/api

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

Keep updating markers on map on top of an overlay image - google maps react

I have the following code which has an overlay image on the map.
However, i would like to display markers on top of this overlay image. The markers come from a users array which is constantly being updated. I want to loop through these markers and place them on the map, but keep updating them if they change longitude/latitude.
I have tried some code below, the data is getting passed to the components correctly, but the markers are always staying in the same position. Could anyone tell me what i'm doing wrong?
Below is my main map code
return (
<div className='map'>
<div className='google-map'>
<GoogleMapReact
bootstrapURLKeys={{ key: 'KEYID' }}
defaultCenter={location}
defaultZoom={zoomLevel}>
{users?.map((user, i) => {
return (
<MyGreatPlace
key={i}
latitude={user.latitude}
longitude={user.longitude}
sender={user.sender}
/>
);
})}
<OverlayImage lat={location.lat} lng={location.lng} text={'A'} />
</GoogleMapReact>
</div>
</div>
);
Below is MyGreatPlace.jsx file for the markers:
export default function MyGreatPlace({ i, latitude, longitude, sender }) {
console.log(sender);
return (
<div style={{ width: 60, height: 60, background: 'green' }}>
{latitude} {longitude} {sender}
</div>
);
}
Below is OverlayImage.jsx file for the overlay image:
export default class OverlayImage extends Component {
static propTypes = {
text: PropTypes.string,
};
static defaultProps = {};
shouldComponentUpdate = shouldPureComponentUpdate;
render() {
return <div>{<img src={floorplan} alt='floorplan' />}</div>;
}
}
Do i need to make the MyGreatPlace component the same as the overlay component? I'm not sure how props would work getting the longitude/latitude passed in?
The solution was to change MyGreatPlace file to the same as the overlay component, to look like this:
export default class MyGreatPlace extends Component {
static propTypes = {
text: PropTypes.string,
};
static defaultProps = {};
shouldComponentUpdate = shouldPureComponentUpdate;
render() {
return <div>{this.prop.text}</div>;
}
}

Remove Zoom control from map in react-leaflet

I am building a react-leaflet application, and I am trying to separate the zoom control from the map itself. The same question in a vanilla Leaflet context was asked here: Placing controls outside map container with Leaflet?. This is what I'm trying to accomplish within the react-leaflet framework. Here is the general outline of my project:
import UIWindow from './UIWindow'
import Map from './MapRL'
class App extends React.Component {
render () {
return (
<div className="App">
<Map />
<UIWindow />
</div>
)
}
}
export default App;
My map component looks like this:
import React from 'react'
import { Map as LeafletMap, TileLayer } from 'react-leaflet'
class Map extends React.Component {
render () {
return(
<LeafletMap
className="sidebar-map"
center={...}
zoom={...}
zoomControl={false}
id="mapId" >
<TileLayer
url="..."
attribution="...
/>
</LeafletMap>
)
}
}
export default Map;
Then my UIWindow looks like this:
class UIWindow extends React.Component {
render () {
return(
<div className="UIWindow">
<Header />
<ControlLayer />
</div>
)
}
}
And finally, my ControlLayer (where I want my ZoomControl to live) should look something like this:
class ControlLayer extends React.Component {
render () {
return (
<div className="ControlLayer">
<LeftSidebar />
<ZoomControl />
{this.props.infoPage && <InfoPage />}
</div>
)
}
}
Of course with this current code, putting ZoomControl in the ControlLayer throws an error: TypeError: Cannot read property '_zoom' of undefined, with some more detailed writeup of what's wrong, with all the references the Leaflet's internal code regarding the zoom functionality.
(DomUtil.removeClass(this._zoomInButton, className);, etc.)
I expected an error, because the ZoomControl is no longer a child of the <Map /> component, but rather a grandchild of the <Map />'s sibling. I know react-leaflet functions on its context provider and consumer, LeafletProvider and LeafletConsumer. When I try to call on my LeafletConsumer from within my <ControlLayer />, I get nothing back. For example:
<LeafletConsumer>
{context => console.log(context)}
</LeafletConsumer>
This logs an empty object. Clearly my LeafletConsumer from my ControlLayer is not properly hooked into the LeaflerProvider from my <Map />. Do I need to export the context from the Map somehow using LeafletProvider? I am a little new to React Context API, so this is not yet intuitive for me. (Most of the rest of the app will be using React Redux to manage state changes and communication between components. This is how I plan to hook up the contents of the sidebar to the map itself. My sidebar doesn't seem to have any problem with being totally disconnected from the <Map />).
How can I properly hook this ZoomControl up to my Map component?
UPDATE:
I tried capturing the context in my redux store, and then serving it to my externalized ZoomControl. Like so:
<LeafletConsumer>
{ context => {
this.props.captureContext(context)
}}
</LeafletConsumer>
This captures the context as part of my redux store. Then I use this as a value in a new context:
// ControlLayer.js
const MapContext = React.createContext()
<MapContext.Provider value={this.props.mapContext}>
<LeftSidebar />
<MapContext.Consumer>
{context => {
if (context){
console.log(ZoomControl);
}
}}
</MapContext.Consumer>
</MapContext.Provider>
Where this.props.mapContext is brought in from my redux matchStateToProps, and its exactly the context captured by the captureContext function.
Still, this is not working. My react dev tools show that the MapContent.Consumer is giving the exact same values as react-leaflet's inherent '' gives when the ZoomControl is within the Map component. But I still get the same error message. Very frustrated over here.
Here is the same approach without hooks:
the Provider should look like this:
class Provider extends Component {
state = { map: null };
setMap = map => {
this.setState({ map });
};
render() {
return (
<Context.Provider value={{ map: this.state.map, setMap: this.setMap }}>
{this.props.children}
</Context.Provider>
);
}
}
Leaflet component will be:
class Leaflet extends Component {
mapRef = createRef(null);
componentDidMount() {
const map = this.mapRef.current.leafletElement;
this.props.setMap(map);
}
render() {
return (
<Map
style={{ width: "80vw", height: "60vh" }}
ref={this.mapRef}
center={[50.63, 13.047]}
zoom={13}
zoomControl={false}
minZoom={3}
maxZoom={18}
>
<TileLayer
attribution='&copy OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?"
/>
</Map>
);
}
}
and now to access the setMap function to the compoenntDidMount you need to do the following:
export default props => (
<Context.Consumer>
{({ setMap }) => <Leaflet {...props} setMap={setMap} />}
</Context.Consumer>
);
For the rest take a look here: Demo
I am not sure how to achieve that using your approach where react-leaflet's wrapper ZoomControl is not a child of Map wrapper when you try to place it outside the Map wrapper.
However, for a small control like the ZoomControl, an easy solution would be to create a custom Zoom component, identical to the original, construct it easily using the native css style and after accessing the map element, invoke the zoom in and out methods respectively.
In the below example I use react-context to save the map element after the map loads:
useEffect(() => {
const map = mapRef.current.leafletElement;
setMap(map);
}, [mapRef, setMap]);
and then here use the map reference to make a custom Zoom component identical to the native (for css see the demo):
const Zoom = () => {
const { map } = useContext(Context);
const zoomIn = e => {
e.preventDefault();
map.setZoom(map.getZoom() + 1);
};
const zoomOut = e => {
e.preventDefault();
map.setZoom(map.getZoom() - 1);
};
return (
<div className="leaflet-bar">
<a
className="leaflet-control-zoom-in"
href="/"
title="Zoom in"
role="button"
aria-label="Zoom in"
onClick={zoomIn}
>
+
</a>
<a
className="leaflet-control-zoom-out"
href="/"
title="Zoom out"
role="button"
aria-label="Zoom out"
onClick={zoomOut}
>
−
</a>
</div>
);
};
and then place it wherever you like:
const App = () => {
return (
<Provider>
<div style={{ display: "flex", flexDirection: "row" }}>
<Leaflet />
<Zoom />
</div>
</Provider>
);
};
Demo

Opening Google Map onClicking the Button | Reactjs

I have a map component, in that, I used Google-map-react library to display locations in google map. Inside that map, I am having a button called "View in maps". So, now, if the user clicks on that button. it should take them to the google map with given location coordinates. Please find code below and let me know, how can I achieve that
import React, { Component } from 'react';
import { withStyles } from '#material-ui/core/styles';
import styles from '../../../../../../assets/css/Me.style.js';
import GoogleMapReact from 'google-map-react';
import { Typography, Button } from '#material-ui/core';
import Marker from '#material-ui/icons/LocationOnOutlined'
class DetailsMap extends Component {
static defaultProps = {
center: { lat: 40.7446790, lng: -73.9485420 },
};
render() {
const {classes} = this.props;
return (
<div className={classes.root}>
<div className={classes.googleMap}>
<GoogleMapReact
zoom = {11}
onClick={this.onClick}
defaultCenter={ this.props.center }
>
<Marker
style={{color: '#ff7777'}}
lat={40.7473310}
lng={-73.8517440}
/>
<div className={classes.address}>
<div className={classes.addressWrapper}>
<Typography className={classes.addressHead}>
Address
</Typography>
<Typography className={classes.addressContent}>
221, raj start, doler street<br />
QC J9PB6X
</Typography>
<Button variant='outlined' className={classes.view}>
View in Maps
</Button>
</div>
</div>
</GoogleMapReact>
</div>
</div>
)
}
}
export default withStyles(styles, {withTheme:true})(DetailsMap);
I use this code for show google map in new tab with specific location with marker.
const showInMapClicked = () => {
window.open("https://maps.google.com?q="+your_lat+","+your_lng );
};
Google maps can take GET parameters for coordinates to open on_load. Make an href to this address
https://www.google.com/maps/#${your_lat},${your_lng},${your_desired_zoom}z
Note
To be able to use the formating provided in the above answer you have to use `` quotes over the normal " " or ' '

Categories

Resources