Reputation: 132
I have written this code in react JS to using "react-google-maps/api" to calculate route between two points. Now my google map keeps rerendering itself until it gives "DIRECTIONS_ROUTE: OVER_QUERY_LIMIT" error. I don't know what's the issue. Help would be appreciated because I am a beginner in react and google-API and also I haven't found a lot of guides of google API in react.
Here is my code:
import React from "react";
import {
GoogleMap,
useLoadScript,
DirectionsService,
DirectionsRenderer,
} from "@react-google-maps/api";
const libraries = ["places", "directions"];
const mapContainerStyle = {
width: "100%",
height: "50vh",
};
const center = {
lat: 31.582045,
lng: 74.329376,
};
const options = {};
const MainMaps = () => {
const { isLoaded, loadError } = useLoadScript({
googleMapsApiKey: "********",
libraries,
});
const [origin2, setOrigin2] = React.useState("lahore");
const [destination2, setDestination2] = React.useState("gujranwala");
const [response, setResponse] = React.useState(null);
const directionsCallback = (response) => {
console.log(response);
if (response !== null) {
if (response.status === "OK") {
setResponse(response);
} else {
console.log("response: ", response);
}
}
};
const mapRef = React.useRef();
const onMapLoad = React.useCallback((map) => {
mapRef.current = map;
}, []);
if (loadError) return "Error loading maps";
if (!isLoaded) return "loading maps";
const DirectionsServiceOption = {
destination: destination2,
origin: origin2,
travelMode: "DRIVING",
};
return (
<div>
<GoogleMap
mapContainerStyle={mapContainerStyle}
zoom={8}
center={center}
onLoad={onMapLoad}
>
{response !== null && (
<DirectionsRenderer
options={{
directions: response,
}}
/>
)}
<DirectionsService
options={DirectionsServiceOption}
callback={directionsCallback}
/>
</GoogleMap>
</div>
);
};
export default MainMaps;
Upvotes: 4
Views: 5670
Reputation: 20
This the component for rendering direction:
import React, { useState} from 'react';
import { DirectionsRenderer, DirectionsService } from '@react-google-maps/api';
function DirectionsMap(props) {
const { origin, destination } = props;
const [response, setResponse] = useState(null);
const directionsCallback = (googleResponse) => {
if (googleResponse) {
if(response) {
if (googleResponse.status === 'OK' && googleResponse.routes.overview_polyline !== response.routes.overview_polyline) {
setResponse(() => googleResponse)
} else {
console.log('response: ', googleResponse)
}
} else {
if (googleResponse.status === 'OK') {
setResponse(() => googleResponse)
} else {
console.log('response: ', googleResponse)
}
}
}
}
return (
<div>
<>
{destination !== '' && origin !== '' && (
<DirectionsService
options={{
origin,
destination,
travelMode: 'DRIVING'
}}
callback={directionsCallback}
/>
)}
{response !== null && (
<DirectionsRenderer
options={{
directions: response
}}
/>
)}
</>
</div>
);
}
export default DirectionsMap;
This is how we needs to call the component:
<GoogleMap
mapContainerStyle={{
width: 'Auto',
height: '400px'
}}
onClick={setCoordinate}
center={center}
zoom={10}
>
<DirectionsMap
origin={origin}
destination={destination}
/>,
</GoogleMap>
Don't forget to call useJsApiLoader
, also remember to add:
const { isLoaded } = useJsApiLoader({
id: 'google-map-script',
googleMapsApiKey: `${import.meta.env.VITE_GOOGLE_MAP_API}`,
libraries: places
})
Upvotes: 0
Reputation: 315
If anybody is still struggling with this, I've handcrafted a solution. I'll post it here, maybe it will help someone.
import * as React from "react";
import {
GoogleMap,
useJsApiLoader,
DirectionsRenderer,
} from "@react-google-maps/api";
interface MapComputationProps {
directionsSetter: Function;
distanceSetter: Function;
origin: string;
destination: string;
googleMapUrl: string;
}
const containerStyle = {
width: "100%",
height: "100%",
};
const center = {
lat: 0,
lng: 0,
};
const MapComponent: any = (props: MapComputationProps) => {
const [directionsState, setDirectionsState] =
React.useState<google.maps.DirectionsResult | null>(null);
const {
directionsSetter,
distanceSetter,
origin,
destination,
googleMapUrl,
} = props;
const { isLoaded } = useJsApiLoader({
googleMapsApiKey: googleMapUrl,
});
React.useEffect(() => {
if (!window.google) {
return;
}
const DirectionsService = new google.maps.DirectionsService();
const MatrixService = new google.maps.DistanceMatrixService();
DirectionsService.route(
{
origin,
destination,
travelMode: google.maps.TravelMode.DRIVING,
unitSystem: google.maps.UnitSystem.METRIC,
},
(result, status) => {
if (status === google.maps.DirectionsStatus.OK) {
setDirectionsState(result);
directionsSetter(result);
} else {
console.error(`error fetching directions ${result}`);
}
}
);
MatrixService.getDistanceMatrix(
{
origins: [origin],
destinations: [destination],
travelMode: google.maps.TravelMode.DRIVING,
unitSystem: google.maps.UnitSystem.METRIC,
},
(result, status) => {
if (status === google.maps.DistanceMatrixStatus.OK) {
distanceSetter(result);
}
}
);
}, [origin, destination]);
if (!isLoaded) {
return "Loading...";
}
return (
<GoogleMap mapContainerStyle={containerStyle} center={center} zoom={1}>
{directionsState && (
<DirectionsRenderer options={{ directions: directionsState }} />
)}
</GoogleMap>
);
};
export default MapComponent;
Upvotes: 2
Reputation: 29
Its a bit late but anyone facing the same issue can refer from this. DirectionService will rerender again and again and you have to stop it until get a response result from DirectionService callback function for DirectionRender postions(lat and lng). You can set a null variable in the beginning and apply condition for DirectionService to run until this variable is null. In the callback of DirectionService set this variable to the response received from this callback and then you will be fine. There was another issue I was facing in DirectionRender class where it was rerendering again and again once I was updating the state. In options, set preserveViewport to true for preserving the view.
You can refer from here for DirectionService issue: https://react-google-maps-api-docs.netlify.app/#directionsrenderer
Upvotes: 2
Reputation: 27
Hey there I am bit late here but you are calling the callback function straight away that is re-rendering and messing up. just change the following.
callback={direactionsCallback}
with this:
callback={(e) => directionsCallback(e)}
Upvotes: -1
Reputation: 398
render should always remain pure. It's a very bad practice to do side effecty things during render.
You are calling setstate continuously in directionCallback. You just need to add another condition count.current < 2.
const [origin, setOrigin] = React.useState('chennai');
const [destination, setDestination] = React.useState('bangalore');
const [response, setResponse] = React.useState(null);
let count = React.useRef(0);
const directionsCallback = res => {
if (res !== null && && count.current < 2) {
if (res.status === 'OK') {
count.current += 1;
setResponse(res);
} else {
count.current = 0;
console.log('res: ', res);
}
}
};
<DirectionsService
options={{
destination: destination,
origin: origin,
travelMode: 'DRIVING'
}}
callback={directionsCallback}
/>
But the response from the callback is not getting rendered in DirectionsRenderer.
I am also not getting any error message.
{response !== null && (
<DirectionsRenderer
// required
options={{
directions: response
}}
/>
)}
The callback response is
Upvotes: 3
Reputation: 768
The rendering issue appears to be with the library itself. One alternative I can suggest is to instead use/load Google Maps API script instead of relying on 3rd party libraries. This way, you can just follow the official documentation provided by Google.
By loading the script, we can now follow their Directions API documentation:
Here is a sample app for your reference: https://stackblitz.com/edit/react-directions-64165413
App.js
import React, { Component } from 'react';
import { render } from 'react-dom';
import Map from './components/map';
import "./style.css";
class App extends Component {
render() {
return (
<Map
id="myMap"
options={{
center: { lat: 31.582045, lng: 74.329376 },
zoom: 8
}}
/>
);
}
}
export default App;
map.js
import React, { Component } from "react";
import { render } from "react-dom";
class Map extends Component {
constructor(props) {
super(props);
this.state = {
map: "",
origin: "",
destination: ""
};
this.handleInputChange = this.handleInputChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
}
onScriptLoad() {
this.state.map = new window.google.maps.Map(
document.getElementById(this.props.id),
this.props.options
);
}
componentDidMount() {
if (!window.google) {
var s = document.createElement("script");
s.type = "text/javascript";
s.src = `https://maps.google.com/maps/api/js?key=YOUR_API_KEY`;
var x = document.getElementsByTagName("script")[0];
x.parentNode.insertBefore(s, x);
// Below is important.
//We cannot access google.maps until it's finished loading
s.addEventListener("load", e => {
this.onScriptLoad();
});
} else {
this.onScriptLoad();
}
}
onSubmit(event) {
this.calculateAndDisplayRoute();
event.preventDefault();
}
calculateAndDisplayRoute() {
var directionsService = new google.maps.DirectionsService();
var directionsRenderer = new google.maps.DirectionsRenderer();
directionsRenderer.setMap(this.state.map);
directionsService.route(
{
origin: { query: this.state.origin },
destination: { query: this.state.destination },
travelMode: "DRIVING"
},
function(response, status) {
if (status === "OK") {
directionsRenderer.setDirections(response);
} else {
window.alert("Directions request failed due to " + status);
}
}
);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
addMarker(latLng) {
var marker = new window.google.maps.Marker({
position: { lat: -33.8569, lng: 151.2152 },
map: this.state.map,
title: "Hello Sydney!"
});
var marker = new google.maps.Marker({
position: latLng,
map: this.state.map
});
}
render() {
return (
<div>
<input
id="origin"
name="origin"
value={this.state.origin}
placeholder="Origin"
onChange={this.handleInputChange}
/>
<input
id="destination"
name="destination"
value={this.state.destination}
placeholder="Destination"
onChange={this.handleInputChange}
/>
<button id="submit" onClick={this.onSubmit}>
Search
</button>
<div className="map" id={this.props.id} />
</div>
);
}
}
export default Map;
Upvotes: 1