react-google-autocomplete
Version:
React component for google autocomplete.
144 lines (122 loc) • 3.92 kB
JavaScript
import { useEffect, useRef, useCallback } from "react";
import { loadGoogleMapScript, isBrowser } from "./utils";
import { GOOGLE_MAP_SCRIPT_BASE_URL } from "./constants";
export default function usePlacesWidget(props) {
const {
ref,
onPlaceSelected,
apiKey,
inputAutocompleteValue = "new-password",
options: {
types = ["(cities)"],
componentRestrictions,
fields = [
"address_components",
"geometry.location",
"place_id",
"formatted_address",
],
bounds,
...options
} = {},
googleMapsScriptBaseUrl = GOOGLE_MAP_SCRIPT_BASE_URL,
language,
} = props;
const inputRef = useRef(null);
const event = useRef(null);
const autocompleteRef = useRef(null);
const observerHack = useRef(null);
const languageQueryParam = language ? `&language=${language}` : "";
const googleMapsScriptUrl = `${googleMapsScriptBaseUrl}?libraries=places&key=${apiKey}${languageQueryParam}`;
const handleLoadScript = useCallback(
() => loadGoogleMapScript(googleMapsScriptBaseUrl, googleMapsScriptUrl),
[googleMapsScriptBaseUrl, googleMapsScriptUrl]
);
useEffect(() => {
const config = {
...options,
fields,
types,
bounds,
};
if (componentRestrictions) {
config.componentRestrictions = componentRestrictions;
}
if (autocompleteRef.current || !inputRef.current || !isBrowser) return;
if (ref && !ref.current) ref.current = inputRef.current;
const handleAutoComplete = () => {
if (typeof google === "undefined")
return console.error(
"Google has not been found. Make sure your provide apiKey prop."
);
if (!google.maps.places)
return console.error("Google maps places API must be loaded.");
if (!inputRef.current instanceof HTMLInputElement)
return console.error("Input ref must be HTMLInputElement.");
autocompleteRef.current = new google.maps.places.Autocomplete(
inputRef.current,
config
);
event.current = autocompleteRef.current.addListener(
"place_changed",
() => {
if (onPlaceSelected && autocompleteRef && autocompleteRef.current) {
onPlaceSelected(
autocompleteRef.current.getPlace(),
inputRef.current,
autocompleteRef.current
);
}
}
);
};
if (apiKey) {
handleLoadScript().then(() => handleAutoComplete());
} else {
handleAutoComplete();
}
return () => (event.current ? event.current.remove() : undefined);
}, []);
// Autofill workaround adapted from https://stackoverflow.com/questions/29931712/chrome-autofill-covers-autocomplete-for-google-maps-api-v3/49161445#49161445
useEffect(() => {
if (
isBrowser &&
window.MutationObserver &&
inputRef.current &&
inputRef.current instanceof HTMLInputElement
) {
observerHack.current = new MutationObserver(() => {
observerHack.current.disconnect();
inputRef.current.autocomplete = inputAutocompleteValue;
});
observerHack.current.observe(inputRef.current, {
attributes: true,
attributeFilter: ["autocomplete"],
});
}
}, [inputAutocompleteValue]);
useEffect(() => {
if (autocompleteRef.current) {
autocompleteRef.current.setFields(fields);
}
}, [fields]);
useEffect(() => {
if (autocompleteRef.current) {
autocompleteRef.current.setBounds(bounds);
}
}, [bounds]);
useEffect(() => {
if (autocompleteRef.current) {
autocompleteRef.current.setComponentRestrictions(componentRestrictions);
}
}, [componentRestrictions]);
useEffect(() => {
if (autocompleteRef.current) {
autocompleteRef.current.setOptions(options);
}
}, [options]);
return {
ref: inputRef,
autocompleteRef,
};
}