Reputation: 3
import React, { useRef, useEffect, useState } from "react";
function GoogleMapsAutocomplete({ onPlaceSelect }) {
const autocompleteInput = useRef(null);
const [autocomplete, setAutocomplete] = useState(null);
useEffect(() => {
const loadGoogleMapsScript = () => {
// Check if the Google Maps script has already been loaded
if (!window.google) {
const script = document.createElement("script");
script.src = `https://maps.googleapis.com/maps/api/js?key=MyAPIKey&libraries=places`;
script.async = true;
script.defer = true;
// Use a callback function to ensure proper initialization
script.onload = () => {
initAutocomplete();
};
document.head.appendChild(script);
} else {
initAutocomplete();
}
};
const initAutocomplete = () => {
const auto = new window.google.maps.places.Autocomplete(
autocompleteInput.current
);
auto.addListener("place_changed", handlePlaceSelect);
setAutocomplete(auto); // Set autocomplete object after it's initialized
};
const handlePlaceSelect = () => {
if (autocomplete) {
// Check if autocomplete is defined
const selectedPlace = autocomplete.getPlace();
if (selectedPlace) {
onPlaceSelect(selectedPlace);
}
}
};
loadGoogleMapsScript();
}, [onPlaceSelect]);
return (
<div>
<input
ref={autocompleteInput}
type="text"
placeholder="Enter a location"
/>
</div>
);
}
export default GoogleMapsAutocomplete;
I'm encountering a persistent error when using the Google Maps JavaScript API in my React application. The error message I'm receiving is:
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'zt') This error seems to be related to the usage of the Google Maps API, but I'm having trouble pinpointing the exact cause. Here's an overview of my setup:
I have a React component (GoogleMapsAutocomplete) that uses the Google Maps API to create an autocomplete input for location search.
I've followed the recommended loading patterns for the Google Maps API, ensuring that the script is loaded with a callback function.
I've also added checks to ensure that the Google Maps API script is loaded only once.
Despite these efforts, the error persists. I suspect that the issue may be related to the timing of when the API is fully initialized or when data is available.
Could anyone provide insights into why I might be encountering this error and how to resolve it? Any help or guidance would be greatly appreciated.
Upvotes: 0
Views: 894
Reputation: 11577
Loading scripts like this can be tricky. It's all exasperated if you are using StrictMode
which runs effects twice under dev under the assumption your effect is truly idempotent. Trying to keep your existing patterns I came up with this.
Essentially, on mount, it instantly stores whether the script is already loaded by checking the global and keeping that in a ref.
Then in the effect, if it is required to be injected but not already loaded, it kicks off the injection process, whilst using another ref to keep track if that already happened. That means if it runs twice before it is actually fully initialised, it won't bother trying to inject it again. initAutocomplete
will be called once the script is done loading.
If it is not required to be injected, initAutocomplete
will be run straight away.
The event listener is registered in an effect which you will notice also returns a cleanup function. If the onPlaceSelect
callback changes that will mean the new handler gets registered.
function GoogleMapsAutocomplete({ onPlaceSelect }) {
const autocompleteInput = useRef(null);
const libInjectionRequired = useRef(!Boolean(window.google));
const libLoading = useRef(false);
const [autocomplete, setAutocomplete] = useState();
const handlePlaceSelect = useCallback(() => {
const selectedPlace = autocomplete.getPlace();
if (selectedPlace) {
onPlaceSelect(selectedPlace);
}
}, [onPlaceSelect, autocomplete]);
useEffect(() => {
const initAutocomplete = () => {
setAutocomplete(
new window.google.maps.places.Autocomplete(autocompleteInput.current)
);
};
// Check if the Google Maps script has already been loaded
if (libInjectionRequired.current) {
if (libLoading.current === true) return;
libLoading.current = true;
const script = document.createElement("script");
script.src = `https://maps.googleapis.com/maps/api/js?key=MyAPIKey&libraries=places`;
script.async = true;
script.defer = true;
// Use a callback function to ensure proper initialization
script.onload = () => {
initAutocomplete();
};
document.head.appendChild(script);
} else {
initAutocomplete();
}
}, []);
useEffect(() => {
let listener;
if (!autocomplete) return;
listener = autocomplete.addListener("place_changed", handlePlaceSelect);
return () => {
if (!listener) return;
window.google.maps.event.removeListener(listener);
};
}, [autocomplete, handlePlaceSelect]);
return (
<div>
<input
ref={autocompleteInput}
type="text"
placeholder="Enter a location"
/>
</div>
);
}
Upvotes: 1