places-autocomplete-hook
Version:
A React hook for Google Places Autocomplete API
193 lines (192 loc) • 6.54 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
usePlacesAutocomplete: () => usePlacesAutocomplete
});
module.exports = __toCommonJS(index_exports);
var import_react = require("react");
function usePlacesAutocomplete({
apiKey,
debounceMs = 300,
language = "en",
types,
sessionToken,
location,
setSelectedPlace
}) {
const [value, setValue] = (0, import_react.useState)("");
const [predictions, setPredictions] = (0, import_react.useState)([]);
const [loading, setLoading] = (0, import_react.useState)(false);
const [error, setError] = (0, import_react.useState)(null);
const debounceTimer = (0, import_react.useRef)(null);
const clear = (0, import_react.useCallback)(() => {
setPredictions([]);
setError(null);
setValue("");
}, []);
const extractAddressComponent = (components, type) => {
const component = components.find((comp) => comp.types.includes(type));
return component?.longText;
};
const getPlaceDetails = (0, import_react.useCallback)(
async (placeId) => {
try {
const response = await fetch(
`https://places.googleapis.com/v1/places/${placeId}?key=${apiKey}&languageCode=${language}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
"X-Goog-FieldMask": "formattedAddress,addressComponents,location",
...sessionToken && { "X-Goog-Api-Key": apiKey }
}
}
);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error?.message || `HTTP error! status: ${response.status}`);
}
const data = await response.json();
const addressComponents = data.addressComponents || [];
return {
placeId,
formattedAddress: data.formattedAddress,
addressComponents,
location: data.location,
streetNumber: extractAddressComponent(addressComponents, "street_number"),
streetName: extractAddressComponent(addressComponents, "route"),
city: extractAddressComponent(addressComponents, "locality"),
state: extractAddressComponent(addressComponents, "administrative_area_level_1"),
country: extractAddressComponent(addressComponents, "country"),
postalCode: extractAddressComponent(addressComponents, "postal_code")
};
} catch (err) {
throw err instanceof Error ? err : new Error("An error occurred while fetching place details");
}
},
[apiKey, language, sessionToken]
);
const handlePlaceSelect = (0, import_react.useCallback)(
async (placeId) => {
setSelectedPlace?.(placeId);
},
[setSelectedPlace]
);
const search = (0, import_react.useCallback)(
async (input) => {
if (!input.trim()) {
clear();
return;
}
try {
setLoading(true);
setError(null);
const requestBody = {
input,
languageCode: language
};
if (types) {
requestBody.types = types;
}
if (location) {
requestBody.locationBias = {
circle: {
center: {
latitude: location.lat,
longitude: location.lng
},
radius: location.radius
}
};
}
const response = await fetch(
`https://places.googleapis.com/v1/places:autocomplete?key=${apiKey}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Goog-FieldMask": "suggestions.placePrediction.place,suggestions.placePrediction.placeId,suggestions.placePrediction.text,suggestions.placePrediction.structuredFormat,suggestions.placePrediction.types",
...sessionToken && { "X-Goog-Api-Key": apiKey }
},
body: JSON.stringify(requestBody)
}
);
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error?.message || `HTTP error! status: ${response.status}`);
}
const data = await response.json();
setPredictions(data.suggestions.map((suggestion) => suggestion.placePrediction) || []);
} catch (err) {
setError(err instanceof Error ? err : new Error("An error occurred"));
setPredictions([]);
} finally {
setLoading(false);
}
},
[apiKey, language, sessionToken, types, location, clear]
);
const debouncedSearch = (0, import_react.useCallback)(
async (input) => {
if (debounceTimer.current) {
clearTimeout(debounceTimer.current);
}
return new Promise((resolve) => {
debounceTimer.current = setTimeout(async () => {
await search(input);
resolve();
}, debounceMs);
});
},
[search, debounceMs]
);
(0, import_react.useEffect)(() => {
return () => {
if (debounceTimer.current) {
clearTimeout(debounceTimer.current);
}
};
}, []);
return {
value,
suggestions: {
status: error ? "ERROR" : loading ? "LOADING" : predictions.length > 0 ? "OK" : "ZERO_RESULTS",
data: predictions
},
setValue: (newValue, shouldFetchData = true) => {
setValue(newValue);
if (shouldFetchData) {
debouncedSearch(newValue);
}
},
clearSuggestions: clear,
search: debouncedSearch,
loading,
error,
getPlaceDetails,
handlePlaceSelect
};
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
usePlacesAutocomplete
});
//# sourceMappingURL=index.js.map