3

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;
Haseeb Ahmad
  • 118
  • 1
  • 13
  • Where is the `LoadScript` component? Also you don't need to use refs check the `onLoad` prop of `GoogleMap` – Alex Mckay Oct 02 '20 at 02:39
  • If I remove **onLoad** its still rerendering unlimited limes. I have used **useLoadScript()** instead of **LoadScript** – Haseeb Ahmad Oct 02 '20 at 02:49

5 Answers5

2

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

enter image description here

Monish N
  • 330
  • 1
  • 6
  • 15
  • This approach helped me in the context of this tutorial: https://testdriven.io/courses/taxi-react/part-three-google-maps/ Note the `else` branch resetting the counter which helps if a user changes the input (->starts typing ->incomplete input get's sent to the server -> error -> counter reset) – erkandem May 10 '23 at 09:21
2

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

1

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;

Negin msr
  • 113
  • 9
Ricky Cuarez
  • 708
  • 4
  • 14
  • Yeah I figured that it is the library issue. Thanks for your answer. – Haseeb Ahmad Nov 04 '20 at 02:44
  • this is not true, the library is ok and the correct answer was given by @Shivam Chamoli this is DirectionsService that is causing the issue as it is continuously calling api despite the response is allready there, you need an if statement around DirectionsService if (!response) DeirectionService – seven Mar 04 '22 at 14:35
1

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;
Moldovan Andrei
  • 305
  • 1
  • 6
  • 19
-1

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