places-autocomplete-hook
Version:
A React hook for Google Places Autocomplete API
264 lines (263 loc) • 9.9 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",
includedPrimaryTypes,
includedRegionCodes,
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 && Array.isArray(comp.types) && comp.types.includes(type)
);
return component?.longText;
};
const getPlaceDetails = (0, import_react.useCallback)(
async (placeId, fields) => {
try {
const defaultFields = ["formattedAddress", "addressComponents", "location"];
const fieldMask = fields && fields.length > 0 ? fields.join(",") : defaultFields.join(",");
const response = await fetch(
`https://places.googleapis.com/v1/places/${placeId}?key=${apiKey}&languageCode=${language}${sessionToken ? `&sessionToken=${sessionToken}` : ""}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
"X-Goog-FieldMask": fieldMask
}
}
);
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 {
accessibilityOptions: data.accessibilityOptions,
addressComponents,
addressDescriptor: data.addressDescriptor,
adrFormatAddress: data.adrFormatAddress,
allowsDogs: data.allowsDogs,
businessStatus: data.businessStatus,
city: extractAddressComponent(addressComponents, "locality"),
country: extractAddressComponent(addressComponents, "country"),
curbsidePickup: data.curbsidePickup,
currentOpeningHours: data.currentOpeningHours,
delivery: data.delivery,
dineIn: data.dineIn,
displayName: data.displayName,
editorialSummary: data.editorialSummary,
formattedAddress: data.formattedAddress,
goodForChildren: data.goodForChildren,
goodForGroups: data.goodForGroups,
goodForWatchingSports: data.goodForWatchingSports,
googleMapsLinks: data.googleMapsLinks,
googleMapsUri: data.googleMapsUri,
iconBackgroundColor: data.iconBackground,
iconMaskBaseUri: data.iconMaskBaseUri,
internationalPhoneNumber: data.internationalPhoneNumber,
liveMusic: data.liveMusic,
location: data.location,
menuForChildren: data.menuForChildren,
name: data.name,
nationalPhoneNumber: data.nationalPhoneNumber,
outdoorSeating: data.outdoorSeating,
parkingOptions: data.parkingOptions,
paymentOptions: data.paymentOptions,
photos: data.photos,
placeId: data.id || placeId,
plusCode: data.plusCode,
postalAddress: data.postalAddress,
postalCode: extractAddressComponent(addressComponents, "postal_code"),
priceLevel: data.priceLevel,
priceRange: data.priceRange,
primaryType: data.primaryType,
primaryTypeDisplayName: data.primaryTypeDisplayName,
pureServiceAreaBusiness: data.pureServiceAreaBusiness,
rating: data.rating,
regularOpeningHours: data.regularOpeningHours,
reservable: data.reservable,
restroom: data.restroom,
reviews: data.reviews,
servesBeer: data.servesBeer,
servesCocktails: data.servesCocktails,
servesDessert: data.servesDessert,
servesDinner: data.servesDinner,
servesLunch: data.servesLunch,
servesWine: data.servesWine,
shortFormattedAddress: data.shortFormattedAddress,
state: extractAddressComponent(addressComponents, "administrative_area_level_1"),
streetName: extractAddressComponent(addressComponents, "route"),
streetNumber: extractAddressComponent(addressComponents, "street_number"),
takeout: data.takeout,
timeZone: data.timeZone,
types: data.types,
userRatingCount: data.userRatingCount,
utcOffsetMinutes: data.utcOffsetMinutes,
viewport: data.viewport,
websiteUri: data.websiteUri
};
} 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 (includedPrimaryTypes) {
requestBody.includedPrimaryTypes = includedPrimaryTypes;
requestBody.includeQueryPredictions = true;
}
if (includedRegionCodes) {
requestBody.includedRegionCodes = includedRegionCodes;
}
if (location) {
requestBody.locationBias = {
circle: {
center: {
latitude: location.lat,
longitude: location.lng
},
radius: location.radius
}
};
}
if (includedPrimaryTypes || includedRegionCodes || location) {
requestBody.includeQueryPredictions = true;
}
if (sessionToken) {
requestBody.sessionToken = sessionToken;
}
let fieldMask = "suggestions.placePrediction.place,suggestions.placePrediction.placeId,suggestions.placePrediction.text,suggestions.placePrediction.structuredFormat,suggestions.placePrediction.types";
if (requestBody.includeQueryPredictions) {
fieldMask += ",suggestions.queryPrediction.text,suggestions.queryPrediction.structuredFormat";
}
const response = await fetch(
`https://places.googleapis.com/v1/places:autocomplete?key=${apiKey}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Goog-FieldMask": fieldMask
},
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();
const placePredictions = data.suggestions.map((suggestion) => suggestion.placePrediction).filter((prediction) => prediction !== void 0 && prediction !== null);
setPredictions(placePredictions);
} catch (err) {
setError(err instanceof Error ? err : new Error("An error occurred"));
setPredictions([]);
} finally {
setLoading(false);
}
},
[apiKey, language, sessionToken, includedPrimaryTypes, includedRegionCodes, 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