react-google-autocomplete
Version:
React component for google autocomplete.
164 lines (140 loc) • 4.64 kB
JavaScript
import React, { 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,
libraries = "places",
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 languageQueryParam = language ? `&language=${language}` : "";
const googleMapsScriptUrl = `${googleMapsScriptBaseUrl}?libraries=${libraries}&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
);
if (autocompleteRef.current) {
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);
}, []);
useEffect(() => {
if (autocompleteRef.current && onPlaceSelected) {
event.current = autocompleteRef.current.addListener("place_changed", function () {
if (onPlaceSelected && autocompleteRef && autocompleteRef.current) {
onPlaceSelected(autocompleteRef.current.getPlace(), inputRef.current, autocompleteRef.current);
}
});
}
return () => (event.current ? event.current.remove() : undefined);
}, [onPlaceSelected]);
// Autofill workaround adapted from https://stackoverflow.com/questions/29931712/chrome-autofill-covers-autocomplete-for-google-maps-api-v3/49161445#49161445
useEffect(() => {
// TODO find out why react 18(strict mode) hangs the page loading
if (
isBrowser &&
window.MutationObserver &&
inputRef.current &&
inputRef.current instanceof HTMLInputElement
) {
const observerHack = new MutationObserver(() => {
observerHack.disconnect();
if (inputRef.current) {
inputRef.current.autocomplete = inputAutocompleteValue;
}
});
observerHack.observe(inputRef.current, {
attributes: true,
attributeFilter: ["autocomplete"],
});
return () => {
observerHack.disconnect(); // Cleanup
};
}
}, [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,
};
}