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.
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.