Home > front end >  React/Material UI - Google Places autocomplete dropdown sometimes doesn't work
React/Material UI - Google Places autocomplete dropdown sometimes doesn't work

Time:12-22

So this code is working, but sometimes on page load or refresh the google places dropdown autocomplete suggestions is not showing up, I can type anything in the searchbox but it just doesn't load the autocomplete box. What could cause this issue?

This is the code.

const apiKey = process.env.REACT_APP_GOOGLE_KEY;
const mapApiJs = 'https://maps.googleapis.com/maps/api/js';
const geocodeJson = 'https://maps.googleapis.com/maps/api/geocode/json';


// load google map api js

function loadAsyncScript(src) {
  return new Promise(resolve => {
    const script = document.createElement("script");
    Object.assign(script, {
      type: "text/javascript",
      async: true,
      src
    })
    script.addEventListener("load", () => resolve(script));
    document.head.appendChild(script);
  })
}

const extractAddress = (place) => {

  const address = {
    city: "",
    state: "",
    zip: "",
    country: "",
    plain() {
        const city = this.city ? this.city   ", " : "";
        const zip = this.zip ? this.zip   ", " : "";
        const state = this.state ? this.state   ", " : "";
        return city   zip   state   this.country;
    }
  }

  if (!Array.isArray(place?.address_components)) {
    return address;
  }

  if(typeof place.geometry.location.lat === 'function') {
    address.lat = place.geometry.location.lat();
  } else {
    address.lat = place.geometry.location.lat;
  }

  if(typeof place.geometry.location.lng === 'function') {
    address.lng = place.geometry.location.lng();
  } else {
    address.lng = place.geometry.location.lng;
  }

  place.address_components.forEach(component => {
    const types = component.types;
    const value = component.long_name;

    if (types.includes("locality")) {
      address.city = value;
    }

    if (types.includes("administrative_area_level_2")) {
      address.state = value;
    }

    if (types.includes("postal_code")) {
      address.zip = value;
    }

    if (types.includes("country")) {
      address.country = value;
    }

  });

  return address;
}


function ReportForm() {

    const searchInput = useRef(null);
    const [address, setAddress] = useState({});

    const [coords, setCoords] = useState({
        lat: 42.680115925419294,
        lng: 14.010667884881462,
    });

    // init gmap script
    const initMapScript = () => {
        // if script already loaded
        if(window.google) {
          return Promise.resolve();
        }
        const src = `${mapApiJs}?key=${apiKey}&libraries=places&v=weekly`;
        return loadAsyncScript(src);
    }

    // do something on address change
    const onChangeAddress = (autocomplete) => {
        const place = autocomplete.getPlace();
        setAddress(extractAddress(place));
        setCoords({
            lat: place.geometry.location.lat(),
            lng: place.geometry.location.lng(),
        });
    }

    // init autocomplete
    const initAutocomplete = () => {
        if (!searchInput.current) return;

        const autocomplete = new window.google.maps.places.Autocomplete(searchInput.current);
        autocomplete.setFields(["address_component", "geometry"]);
        autocomplete.addListener("place_changed", () => onChangeAddress(autocomplete));

    }

    const reverseGeocode = ({ latitude: lat, longitude: lng}) => {
        const url = `${geocodeJson}?key=${apiKey}&latlng=${lat},${lng}`;
        searchInput.current.value = "Getting your location...";
        fetch(url)
            .then(response => response.json())
            .then(location => {
              const place = location.results[0];
              const _address = extractAddress(place);
              setAddress(_address);
              searchInput.current.value = _address.plain();
              setCoords({
                lat: lat,
                lng: lng,
              });
            })
    }

    const findMyLocation = () => {
        if (navigator.geolocation) {
          navigator.geolocation.getCurrentPosition(position => {
            reverseGeocode(position.coords)
          })
        }
    }

    // load map script after mounted
    useEffect(() => {
        initMapScript().then(() => initAutocomplete())
    }, []); 

    return(
        <>        
            <form>                    
                <div>
                    <div className="search">
                        <span><Search /></span>
                        <input 
                            name="address" 
                            ref={searchInput} 
                            type="text" 
                            placeholder="Search Address...." 
                        />
                        <button type="button" onClick={findMyLocation}><GpsFixed /></button>
                    </div>
                </div>                      
            </form>                       
        </>
    );
}

I have also tried with Material UI TextField and inputRef instead of the standard input, but I get the same problem. Also, it happens more often on page refresh. On first page load it almost works every time.

CodePudding user response:

Have you tried adding searchInput.current to the dependency array of useEffect? You have this line if (!searchInput.current) return; inside initAutocomplete and your searchInput is initially set to null. So on page refresh it's possible there is a race condition between the ref being set on the input element and the running of the initAutocomplete function.

  • Related