@yext/search-ui-react
Version:
A library of React Components for powering Yext Search integrations
85 lines (79 loc) • 3.2 kB
text/typescript
import { useTranslation } from 'react-i18next';
import { Matcher, SelectableStaticFilter, useSearchActions, useSearchState } from '@yext/search-headless-react';
import { executeSearch } from '../utils/search-operations';
import { getUserLocation } from '../utils/location-operations';
import { useCallback, useState } from 'react';
const GEOLOCATION_FIELD_ID = 'builtin.location';
const LOCATION_FIELD_IDS = [GEOLOCATION_FIELD_ID, 'builtin.region', 'address.countryCode'];
const METERS_PER_MILE = 1609.344;
/**
* The props for {@link useGeolocationHandler} hook.
*
* @internal
*/
interface GeolocationHandlerArgs {
/** Configuration used when collecting the user's location. */
geolocationOptions?: PositionOptions,
/**
* The radius, in miles, around the user's location to find results. Defaults to 50.
* If location accuracy is low, a larger radius may be used automatically.
*/
radius?: number,
/** Custom handler function to call after user's position is successfully determined. */
handleUserPosition?: (position: GeolocationPosition) => void
}
/**
* Creates a function to collect user's geolocation and, by default, will set
* a built-in location filter and execute a search.
*
* @internal
*
* @param props - {@link GeolocationHandlerArgs}
* @returns - A function to collect and process user's geolocation
* - A boolean to indicate if user's geolocation is being fetch
*/
export function useGeolocationHandler({
geolocationOptions,
radius = 50,
handleUserPosition
}: GeolocationHandlerArgs): [() => Promise<void>, boolean] {
const { t } = useTranslation();
const [isFetchingUserLocation, setIsFetchingUserLocation] = useState<boolean>(false);
const searchActions = useSearchActions();
const staticFilters = useSearchState(s => s.filters.static || []);
const defaultHandleUserPosition = useCallback((position: GeolocationPosition) => {
const { latitude, longitude, accuracy } = position.coords;
const locationFilter: SelectableStaticFilter = {
displayName: t('currentLocation'),
selected: true,
filter: {
kind: 'fieldValue',
fieldId: GEOLOCATION_FIELD_ID,
matcher: Matcher.Near,
value: {
lat: latitude,
lng: longitude,
radius: Math.max(accuracy, radius * METERS_PER_MILE)
},
}
};
const nonLocationFilters = staticFilters.filter(filter => {
return !(filter.filter.kind === 'fieldValue'
&& LOCATION_FIELD_IDS.includes(filter.filter.fieldId));
});
searchActions.setStaticFilters([...nonLocationFilters, locationFilter]);
executeSearch(searchActions);
}, [radius, searchActions, staticFilters]);
const geolocationHandler = useCallback(async () => {
setIsFetchingUserLocation(true);
try {
const position = await getUserLocation(geolocationOptions);
(handleUserPosition ?? defaultHandleUserPosition)(position);
} catch (e) {
console.warn(e);
} finally {
setIsFetchingUserLocation(false);
}
}, [setIsFetchingUserLocation, geolocationOptions, handleUserPosition, defaultHandleUserPosition]);
return [geolocationHandler, isFetchingUserLocation];
}