UNPKG

places-autocomplete-hook

Version:
168 lines 5.34 kB
// src/index.ts import { useState, useCallback, useEffect, useRef } from "react"; function usePlacesAutocomplete({ apiKey, debounceMs = 300, language = "en", types, sessionToken, location, setSelectedPlace }) { const [value, setValue] = useState(""); const [predictions, setPredictions] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const debounceTimer = useRef(null); const clear = useCallback(() => { setPredictions([]); setError(null); setValue(""); }, []); const extractAddressComponent = (components, type) => { const component = components.find((comp) => comp.types.includes(type)); return component?.longText; }; const getPlaceDetails = 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 = useCallback( async (placeId) => { setSelectedPlace?.(placeId); }, [setSelectedPlace] ); const search = 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 = useCallback( async (input) => { if (debounceTimer.current) { clearTimeout(debounceTimer.current); } return new Promise((resolve) => { debounceTimer.current = setTimeout(async () => { await search(input); resolve(); }, debounceMs); }); }, [search, debounceMs] ); 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 }; } export { usePlacesAutocomplete }; //# sourceMappingURL=index.mjs.map