I am trying to display the user location on the map using google-maps-react. I followed the fullstack tutorial, but I just can't seem to display the user location. I will display my Map.js Component below. Please help me point out what I am doing wrong. Thank you.
import React, { Component } from 'react'
import ReactDOM from 'react-dom';
class Map extends Component {
constructor(props) {
super(props);
const {lat, lng} = this.props.initialCenter;
this.state = {
currentLocation: {
lat: lat,
lng: lng
}
}
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.google !== this.props.google) {
this.loadMap();
}
if (prevState.currentLocation !== this.state.currentLocation) {
this.recenterMap();
}
}
recenterMap() {
const map = this.map;
const curr = this.state.currentLocation;
const google = this.props.google;
const maps = google.maps;
if (map) {
let center = new maps.LatLng(curr.lat, curr.lng)
map.panTo(center)
}
}
componentDidMount() {
if (this.props.centerAroundCurrentLocation) {
if (navigator && navigator.geolocation) {
navigator.geolocation.getCurrentPosition((pos) => {
const coords = pos.coords;
this.setState({
currentLocation: {
lat: coords.latitude,
lng: coords.longitude
}
})
})
}
}
this.loadMap();
}
loadMap() {
if (this.props && this.props.google) {
// google is available
const {google} = this.props;
const maps = google.maps;
const mapRef = this.refs.map;
const node = ReactDOM.findDOMNode(mapRef);
let {initialCenter, zoom} = this.props;
const {lat, lng} = initialCenter;
const center = new maps.LatLng(lat, lng);
const mapConfig = Object.assign({}, {
center: center,
zoom: zoom
})
this.map = new maps.Map(node, mapConfig);
}
}
render() {
const style = {
width: '100vw',
height: '100vh'
}
return (
<div ref='map' style={style}>
Loading map...
</div>
)
}
}
Map.propTypes = {
google: React.PropTypes.object,
zoom: React.PropTypes.number,
initialCenter: React.PropTypes.object,
centerAroundCurrentLocation: React.PropTypes.bool
}
Map.defaultProps = {
zoom: 13,
// San Francisco, by default
initialCenter: {
lat: 37.774929,
lng: -122.419416
},
centerAroundCurrentLocation: false
}
export default Map
Related
I'm using Next.js version 12.3.1 and React.js 18.2.0. When I select point A and point B the direction service and direction renderer gets called and the map prints the direction with the markers. But when I change points A and B the map prints a new direction but the very first direction/line stays on the map.
How do I fix the issue?
Here is the Map functions file:
import React, { useCallback, useState } from "react";
import CustomGoogleMap from "#components/core/CustomGoogleMap";
import { Box } from "#mui/material";
import { anyObjectType } from "#services/types";
import MapAPI from "#services/api/map";
import { CreateBookingActions } from "#services/constants";
import { useCreateBookingContext } from "#contexts/CreateBookingContext";
const BookingMap: React.FC<BookingMapProps> = ({
memoizedloadingPoint,
memoizedunloadingPoint,
memoizedwaypointsList,
setToast,
setLoadingPoint,
setUnLoadingPoint,
}) => {
const cbCtx = useCreateBookingContext();
const { state, dispatch } = cbCtx;
const { waypointsList } = state;
const [call, setCall] = useState(false);
const handleLoadingPointDragLocation = useCallback(
async (lat: number, lng: number) => {
if (lat && lng) {
const query = {
latlng: `${lat},${lng}`,
};
const { response, error }: any = await MapAPI.onDrag(query);
if (response) {
setLoadingPoint({
...response.data.data,
});
} else {
setToast("Somthing went wrong", "error");
}
}
},
[]
);
const handleUnLoadingPointDragLocation = useCallback(
async (lat: number, lng: number) => {
if (lat && lng) {
const query = {
latlng: `${lat},${lng}`,
};
const { response, error }: any = await MapAPI.onDrag(query);
if (response) {
setUnLoadingPoint({
...response.data.data,
});
} else {
setToast("Somthing went wrong", "error");
}
}
},
[]
);
const setWaypointsList = (val: any[]) => {
dispatch({
type: CreateBookingActions.SET_WAYPOINT_LIST,
payload: val,
});
};
const handleStopageDragLocation = useCallback(
async (lat: number, lng: number, index: number) => {
if (lat && lng) {
const query = {
latlng: `${lat},${lng}`,
};
const { response, error }: any = await MapAPI.onDrag(query);
if (response) {
const updatedWaypoints = JSON.parse(JSON.stringify(waypointsList));
updatedWaypoints.splice(index, 1, { ...response.data.data });
setWaypointsList([...updatedWaypoints]);
setCall(true);
} else {
setToast("Somthing went wrong", "error");
}
}
},
[]
);
return (
<Box sx={{ height: "420px" }}>
<CustomGoogleMap
call={call}
setCall={setCall}
loadingP={memoizedloadingPoint}
unloadingP={memoizedunloadingPoint}
waypointsList2={memoizedwaypointsList}
handleLoadingPointDragLocation={handleLoadingPointDragLocation}
handleUnLoadingPointDragLocation={handleUnLoadingPointDragLocation}
handleStopageDragLocation={handleStopageDragLocation}
/>
</Box>
);
};
export default BookingMap;
interface BookingMapProps {
memoizedloadingPoint: anyObjectType;
memoizedunloadingPoint: anyObjectType;
memoizedwaypointsList: any[];
setToast: (a: string, b: string) => void;
setLoadingPoint: (a: anyObjectType) => void;
setUnLoadingPoint: (a: anyObjectType) => void;
}
Here is the google map file:
import React, { useEffect, useState, memo } from "react";
import {
GoogleMap,
MarkerF,
DirectionsRenderer,
DirectionsService,
InfoWindow,
useLoadScript,
LoadScriptNext,
} from "#react-google-maps/api";
import LoadingMarker from "../SvgIcons/loading-marker.svg";
import UnLoadingMarker from "../SvgIcons/unloading-marker.svg";
import StoppageIcon from "../SvgIcons/stopageIcon.svg";
import { CircularProgress } from "#mui/material";
import { Box } from "#mui/system";
import { isEmptyArray } from "#services/utils";
import { anyObjectType, Dispatcher } from "#services/types";
const containerStyle = {
width: "100%",
height: "100%",
};
const center = {
lat: 23.810331,
lng: 90.412521,
};
interface CustomLatLong {
latitude: number;
longitude: number;
textEn: string;
}
type CustomDirectionsWaypoint = google.maps.DirectionsWaypoint & CustomLatLong;
interface CustomGoogleMapProps {
call: Boolean;
setCall: Dispatcher<boolean>;
handleStopageDragLocation?: (a: number, b: number, c: number) => void;
handleLoadingPointDragLocation?: (a: number, b: number) => void;
handleUnLoadingPointDragLocation?: (a: number, b: number) => void;
waypointsList2: CustomDirectionsWaypoint[];
loadingP: anyObjectType;
unloadingP: anyObjectType;
isAddressModal?: Boolean;
}
const CustomGoogleMap: React.FC<CustomGoogleMapProps> = (props) => {
const {
call,
setCall,
loadingP,
unloadingP,
waypointsList2,
handleLoadingPointDragLocation,
handleUnLoadingPointDragLocation,
handleStopageDragLocation,
isAddressModal = false,
} = props;
// const { isLoaded, loadError } = useLoadScript({
// googleMapsApiKey: process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY!,
// });
const [infoBox, setInfoWindow] = useState({
position: {
lat: 0,
lng: 0,
},
info: "",
});
const [directions, setDirections] = useState(null);
console.log("point:", loadingP, unloadingP);
useEffect(() => {
if (loadingP.latitude && unloadingP.latitude) {
setCall(true);
}
}, [loadingP.latitude, unloadingP.latitude]);
const directionsCallback = (response: any) => {
if (response !== null) {
console.log("directionsCallback called");
if (response.status === "OK") {
setCall(false);
setDirections(response);
}
}
};
const getDirectionRender = () => {
return (
<DirectionsService
options={{
destination: {
lat: unloadingP.latitude,
lng: unloadingP.longitude,
},
origin: {
lat: loadingP.latitude,
lng: loadingP.longitude,
},
travelMode: google.maps.TravelMode.DRIVING,
waypoints: waypointsList2.map(({ latitude, longitude }) => {
return {
location: {
lat: latitude,
lng: longitude,
}.toString(),
stopover: false,
};
}),
}}
callback={directionsCallback}
/>
);
};
// if (loadError) {
// return <div>Map cannot be loaded right now, sorry.</div>;
// }
const renderMap = () => {
return (
<LoadScriptNext
googleMapsApiKey={process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY!}
>
<GoogleMap
mapContainerStyle={containerStyle}
center={
loadingP.latitude
? {
lat: loadingP.latitude,
lng: loadingP.longitude,
}
: center
}
zoom={10}
onClick={({ latLng }) => {
isAddressModal &&
handleLoadingPointDragLocation &&
handleLoadingPointDragLocation(latLng!.lat(), latLng!.lng());
}}
>
{loadingP.latitude &&
unloadingP.latitude &&
call &&
getDirectionRender()}
{directions && (
<DirectionsRenderer
options={{
directions: directions,
suppressMarkers: true,
}}
/>
)}
{infoBox.position.lat && (
<InfoWindow
// options={infoBoxOptions}
position={infoBox.position}
onCloseClick={() =>
setInfoWindow({
position: {
lat: 0,
lng: 0,
},
info: "",
})
}
>
<p> {infoBox.info}</p>
</InfoWindow>
)}
<MarkerF
key={loadingP.latitude}
draggable
onDragEnd={({ latLng }) => {
handleLoadingPointDragLocation &&
handleLoadingPointDragLocation(latLng!.lat(), latLng!.lng());
}}
position={{ lat: loadingP.latitude, lng: loadingP.longitude }}
icon={LoadingMarker}
onClick={() =>
setInfoWindow({
...infoBox,
position: {
lat: loadingP.latitude,
lng: loadingP.longitude,
},
info: loadingP.textEn,
})
}
/>
<MarkerF
key={unloadingP.latitude}
draggable
onDragEnd={({ latLng }) => {
handleUnLoadingPointDragLocation &&
handleUnLoadingPointDragLocation(latLng!.lat(), latLng!.lng());
}}
position={{
lat: unloadingP.latitude,
lng: unloadingP.longitude,
}}
icon={UnLoadingMarker}
onClick={() =>
setInfoWindow({
...infoBox,
position: {
lat: unloadingP.latitude,
lng: unloadingP.longitude,
},
info: unloadingP.textEn,
})
}
/>
{!isEmptyArray(waypointsList2) &&
waypointsList2.map((item, index) => {
return (
<MarkerF
key={index}
draggable
visible
onDragEnd={({ latLng }) => {
handleStopageDragLocation &&
handleStopageDragLocation(
latLng!.lat(),
latLng!.lng(),
index
);
}}
position={{
lat: item.latitude,
lng: item.longitude,
}}
icon={StoppageIcon}
onClick={() =>
setInfoWindow({
...infoBox,
position: {
lat: item.latitude,
lng: item.longitude,
},
info: item.textEn,
})
}
/>
);
})}
</GoogleMap>
</LoadScriptNext>
);
};
return renderMap();
};
export default memo(CustomGoogleMap);
I have methods of rendering Markers as shown below. I'm passing markers array from props and rendering it each time componentDidUpdate triggered. The problem is my old markers is not removing from maps. For example if I had 1 coordinates inside my parent component and update it with new ones, the new one appears and the old one stands still.
`
import React from 'react';
const google = window.google;
export class GMap extends React.Component {
mapRef = React.createRef();
directionsService
directionsRenderer
map;
componentDidMount() {
this.initMap();
const { onClick } = this.props;
onClick && this.onMapClick();
}
componentDidUpdate() {
const { markers } = this.props;
this.calcRoute();
if (markers && markers.length > 0) {
this.clear(markers);
this.renderMarkers(markers);
}
}
initMap() {
this.directionsService = new google.maps.DirectionsService();
this.directionsRenderer = new google.maps.DirectionsRenderer();
const mapOptions = {
zoom: 13,
center: { lat: 40.386119, lng: 49.860925 }
}
const map = new google.maps.Map(document.getElementById('map'), mapOptions);
this.map = map;
this.directionsRenderer.setMap(map);
}
onMapClick() {
this.map.addListener('click', (e) => {
this.props.onClick(e);
})
}
renderMarkers(markers) {
markers.forEach(position => {
const marker = new google.maps.Marker({ position });
marker.setMap(this.map);
})
}
calcRoute() {
const { directions } = this.props;
if (directions) {
const [{ lat: fLat, lng: fLng }, { lat: tLat, lng: tLng }] = directions;
if (fLat && fLng && tLat && tLng) {
var request = {
origin: { lat: fLat, lng: fLng },
destination: { lat: tLat, lng: tLng },
travelMode: 'DRIVING'
};
this.directionsService.route(request, (result, status) => {
if (status === 'OK') {
this.directionsRenderer.setDirections(result);
}
});
}
}
}
render() {
return (
<div id='map' ref={this.mapRef} />
)
}
}
`
How are you removing the markers? All I can see in the posted code is this.clear(markers) with no reference to clear. Try doing something like this:
clear(markers) {
for(let i = 0; i < markers.length; i++) {
markers[i].setMap(null);
}
}
Hope this helps!
I'm traying to draw a route between two points with react-google-maps but is not working for me. Can u help me with this problem? Here's a example of my react component.
import React from 'react';
import {
withScriptjs,
withGoogleMap,
GoogleMap,
Marker,
} from 'react-google-maps';
import MapDirectionsRenderer from './app_map_directions_render';
const Map = withScriptjs(
withGoogleMap(props => (
<GoogleMap
defaultCenter={props.defaultCenter}
defaultZoom={props.defaultZoom}
>
{props.places.map((marker, index) => {
const position = {lat: marker.latitude, lng: marker.longitude};
return <Marker key={index} position={position}/>;
})}
<MapDirectionsRenderer places={props.places} travelMode={window.google.maps.TravelMode.DRIVING} />
</GoogleMap>
))
);
const AppMap = props => {
const {places} = props;
const {
loadingElement,
containerElement,
mapElement,
defaultCenter,
defaultZoom
} = props;
return (
<Map
googleMapURL={
'https://maps.googleapis.com/maps/api/js?key=' +
googleMapsApiKey +
'&v=3.exp&libraries=geometry,drawing,places'
}
places={places}
loadingElement={loadingElement || <div style={{height: `100%`}}/>}
containerElement={containerElement || <div style={{height: "80vh"}}/>}
mapElement={mapElement || <div style={{height: `100%`}}/>}
defaultCenter={defaultCenter || {lat: 25.798939, lng: -80.291409}}
defaultZoom={defaultZoom || 11}
/>
);
};
export default AppMap;
And my MapDirectionsRenderer Component
import React, {Component} from 'react';
import { DirectionsRenderer } from "react-google-maps";
export default class MapDirectionsRenderer extends Component {
state = {
directions: null,
error: null
};
componentDidMount() {
const { places, travelMode } = this.props;
const waypoints = places.map(p =>({
location: {lat: p.latitude, lng: p.longitude},
stopover: true
}))
if(waypoints.length >= 2){
const origin = waypoints.shift().location;
const destination = waypoints.pop().location;
const directionsService = new window.google.maps.DirectionsService();
directionsService.route(
{
origin: origin,
destination: destination,
travelMode: travelMode,
waypoints: waypoints
},
(result, status) => {
if (status === window.google.maps.DirectionsStatus.OK) {
this.setState({
directions: result
});
} else {
this.setState({ error: result });
}
}
);
}
}
render() {
if (this.state.error) {
return <h1>{this.state.error}</h1>;
}
return <DirectionsRenderer directions={this.state.directions} />;
}
}
To render a route Google Maps API provides Directions Service, in case of react-google-maps library DirectionsRenderer component is available which is a wrapper around DirectionsRenderer class which in turn:
Renders directions obtained from the DirectionsService.
Assuming the data for route is provided in the following format:
const places = [
{latitude: 25.8103146,longitude: -80.1751609},
{latitude: 27.9947147,longitude: -82.5943645},
{latitude: 28.4813018,longitude: -81.4387899},
//...
]
the following component could be introduced to calculate and render directions via react-google-maps library:
class MapDirectionsRenderer extends React.Component {
state = {
directions: null,
error: null
};
componentDidMount() {
const { places, travelMode } = this.props;
const waypoints = places.map(p =>({
location: {lat: p.latitude, lng:p.longitude},
stopover: true
}))
const origin = waypoints.shift().location;
const destination = waypoints.pop().location;
const directionsService = new google.maps.DirectionsService();
directionsService.route(
{
origin: origin,
destination: destination,
travelMode: travelMode,
waypoints: waypoints
},
(result, status) => {
if (status === google.maps.DirectionsStatus.OK) {
this.setState({
directions: result
});
} else {
this.setState({ error: result });
}
}
);
}
render() {
if (this.state.error) {
return <h1>{this.state.error}</h1>;
}
return <DirectionsRenderer directions={this.state.directions} />;
}
}
Here is a demo
For React 16.8 or above, MapDirectionsRenderer could be implemented (using Hooks) as below:
function MapDirectionsRenderer(props) {
const [directions, setDirections] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
const { places, travelMode } = props;
const waypoints = places.map(p => ({
location: { lat: p.latitude, lng: p.longitude },
stopover: true
}));
const origin = waypoints.shift().location;
const destination = waypoints.pop().location;
const directionsService = new google.maps.DirectionsService();
directionsService.route(
{
origin: origin,
destination: destination,
travelMode: travelMode,
waypoints: waypoints
},
(result, status) => {
console.log(result)
if (status === google.maps.DirectionsStatus.OK) {
setDirections(result);
} else {
setError(result);
}
}
);
});
if (error) {
return <h1>{error}</h1>;
}
return (
directions && (
<DirectionsRenderer directions={directions} />
)
);
}
I want to make two components: App and Map. However when I try to make a new brand Map component and send the data from App to Map component, I cannot.
My App (default) component holds the data as a state. When I try to send this state to the Map component. It holds the data as a prop.
And of course If I don't separate them and write everything in App.js, everything works as I expected (markers shown on the map). But I want to control all states in the parent component.
Am I violating a fundamental React rule? How can I fix that?
App.js
import React, { Component } from "react";
import "./App.css";
import Map from "./Map";
class App extends Component {
constructor(props) {
super(props);
this.state = {
locations: [],
markers: []
};
}
componentDidMount() {
fetch(
"correct_foursquare_api_url"
)
.then(response => response.json())
.then(data =>
data.response.venues.map(place => ({
id: place.id,
name: place.name,
lat: place.location.lat,
lng: place.location.lng
}))
)
.then(locations => {
this.setState({ locations });
});
}
render() {
return (
<div className="App">
<Map locations={this.state.locations} />
</div>
)
}
}
export default App;
Map.js
import React, { Component } from "react";
/* global google */
class Map extends Component {
constructor(props) {
super(props);
this.state = {
locations: [],
markers: []
};
}
componentDidMount() {
this.callMap();
}
callMap() {
window.initMap = this.initMap;
loadJS(
"api_url"
);
}
// Map
initMap = () => {
const { locations, markers } = this.state;
let map = new google.maps.Map(document.getElementById("map"), {
center: { lat: 59.4827293, lng: -83.1405355 },
zoom: 13
});
// Markers
for (var i = 0; i < locations.length; i++) {
var title = locations[i].name;
var position = new google.maps.LatLng(locations[i].lat, locations[i].lng);
var id = locations[i].id;
var marker = new google.maps.Marker({
map: map,
position: position,
title: title,
animation: google.maps.Animation.DROP,
id: id
});
markers.push(marker);
}
};
render() {
return <div id="map" />;
}
}
function loadJS(src) {
var ref = window.document.getElementsByTagName("script")[0];
var script = window.document.createElement("script");
script.src = src;
script.async = true;
ref.parentNode.insertBefore(script, ref);
}
export default Map;
You store your locations in the state of the App component, but you also have locations in state of the Map component that you use on mount.
You could instead wait with rendering the Map component until the locations request is finished, and then use the locations props in the Map component passed down from App instead.
Example
class App extends Component {
constructor(props) {
super(props);
this.state = {
locations: [],
markers: []
};
}
componentDidMount() {
fetch("correct_foursquare_api_url")
.then(response => response.json())
.then(data => {
const locations = data.response.venues.map(place => ({
id: place.id,
name: place.name,
lat: place.location.lat,
lng: place.location.lng
}));
this.setState({ locations });
});
}
render() {
const { locations, markers } = this.state;
if (locations.length === 0) {
return null;
}
return (
<div className="App">
<Map locations={locations} markers={markers} />
</div>
);
}
}
class Map extends Component {
// ...
initMap = () => {
const { locations, markers } = this.props;
// ...
};
// ...
}
I'm new to React and currently trying to learn how to use react-google-maps library. Tried to show a map with users geolocation as the initialCenter of the map.
This is my code:
import React from "react";
import { GoogleApiWrapper, Map } from "google-maps-react";
export class MapContainer extends React.Component {
constructor(props) {
super(props);
this.state = { userLocation: { lat: 32, lng: 32 } };
}
componentWillMount(props) {
this.setState({
userLocation: navigator.geolocation.getCurrentPosition(
this.renderPosition
)
});
}
renderPosition(position) {
return { lat: position.coords.latitude, lng: position.coords.longitude };
}
render() {
return (
<Map
google={this.props.google}
initialCenter={this.state.userLocation}
zoom={10}
/>
);
}
}
export default GoogleApiWrapper({
apiKey: "-----------"
})(MapContainer);
Insted of creating a map with users location I get an initialCenter of my default state values.
How can I fix it? Am I even using the lifecycle function right?
Thank you very much for your help
navigator.geolocation.getCurrentPosition is asynchronous, so you need to use the success callback and set the user location in there.
You could add an additional piece of state named e.g. loading, and only render when the user's geolocation is known.
Example
export class MapContainer extends React.Component {
state = { userLocation: { lat: 32, lng: 32 }, loading: true };
componentDidMount(props) {
navigator.geolocation.getCurrentPosition(
position => {
const { latitude, longitude } = position.coords;
this.setState({
userLocation: { lat: latitude, lng: longitude },
loading: false
});
},
() => {
this.setState({ loading: false });
}
);
}
render() {
const { loading, userLocation } = this.state;
const { google } = this.props;
if (loading) {
return null;
}
return <Map google={google} initialCenter={userLocation} zoom={10} />;
}
}
export default GoogleApiWrapper({
apiKey: "-----------"
})(MapContainer);