react-google-maps/api DirectionsService keeps rerendering itself

4.8k views Asked by At

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;
6

There are 6 answers

2
Ricky Cuarez On BEST ANSWER

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;

2
Nitesh Raut Kshettri On

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

1
Monish N On

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

1
Moldovan Andrei On

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;
0
Abhijith M R On

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
})
0
Shivam Chamoli On

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