@yext/search-headless-react
Version:
The official React UI Bindings layer for Search Headless
49 lines (44 loc) • 1.59 kB
text/typescript
import { useCallback, useContext, useEffect, useRef } from 'react';
import { State } from '@yext/search-headless';
import { SearchHeadlessContext } from './SearchHeadlessContext';
import { useSyncExternalStoreWithSelector } from 'use-sync-external-store/shim/with-selector.js';
export type StateSelector<T> = (s: State) => T;
/**
* Returns the Search State returned by the map function.
* Uses "use-sync-external-store/shim" to handle reading
* and subscribing from external store in React version
* pre-18 and 18.
*/
export function useSearchState<T>(stateSelector: StateSelector<T>): T {
const search = useContext(SearchHeadlessContext);
if (search.state === undefined) {
throw new Error('Attempted to call useSearchState() outside of SearchHeadlessProvider.'
+ ' Please ensure that \'useSearchState()\' is called within an SearchHeadlessProvider component.');
}
const getSnapshot = useCallback(() => search.state, [search.state]);
const isMountedRef = useRef<boolean>(false);
useEffect(() => {
isMountedRef.current = true;
return () => {
isMountedRef.current = false;
};
}, []);
const subscribe = useCallback(cb =>
search.addListener({
valueAccessor: state => state,
callback: () => {
// prevent React state update on an unmounted component
if (!isMountedRef.current) {
return;
}
cb();
}
}), [search]);
const selectedState = useSyncExternalStoreWithSelector<State, T>(
subscribe,
getSnapshot,
getSnapshot,
stateSelector
);
return selectedState;
}