react-bootstrap-typeahead
Version:
React typeahead with Bootstrap styling
81 lines (80 loc) • 3.23 kB
JavaScript
import debounce from 'lodash.debounce';
import PropTypes from 'prop-types';
import React, { forwardRef, useCallback, useEffect, useRef, } from 'react';
import useForceUpdate from '@restart/hooks/useForceUpdate';
import usePrevious from '@restart/hooks/usePrevious';
import { optionType } from '../propTypes';
import { getDisplayName, isFunction, warn } from '../utils';
const propTypes = {
delay: PropTypes.number,
isLoading: PropTypes.bool.isRequired,
minLength: PropTypes.number,
onSearch: PropTypes.func.isRequired,
options: PropTypes.arrayOf(optionType),
promptText: PropTypes.node,
searchText: PropTypes.node,
useCache: PropTypes.bool,
};
export function useAsync(props) {
const { allowNew, delay = 200, emptyLabel, isLoading, minLength = 2, onInputChange, onSearch, options = [], promptText = 'Type to search...', searchText = 'Searching...', useCache = true, ...otherProps } = props;
const cacheRef = useRef({});
const handleSearchDebouncedRef = useRef(null);
const queryRef = useRef(props.defaultInputValue || '');
const forceUpdate = useForceUpdate();
const prevProps = usePrevious(props);
const handleSearch = useCallback((query) => {
queryRef.current = query;
if (!query || (minLength && query.length < minLength)) {
return;
}
if (useCache && cacheRef.current[query]) {
forceUpdate();
return;
}
onSearch(query);
}, [forceUpdate, minLength, onSearch, useCache]);
useEffect(() => {
handleSearchDebouncedRef.current = debounce(handleSearch, delay);
return () => {
handleSearchDebouncedRef.current &&
handleSearchDebouncedRef.current.cancel();
};
}, [delay, handleSearch]);
useEffect(() => {
if (!isLoading && prevProps && prevProps.isLoading && useCache) {
cacheRef.current[queryRef.current] = options;
}
});
const getEmptyLabel = () => {
if (!queryRef.current.length) {
return promptText;
}
if (isLoading) {
return searchText;
}
return emptyLabel;
};
const handleInputChange = useCallback((query, e) => {
onInputChange && onInputChange(query, e);
handleSearchDebouncedRef.current &&
handleSearchDebouncedRef.current(query);
}, [onInputChange]);
const cachedQuery = cacheRef.current[queryRef.current];
return {
...otherProps,
allowNew: isFunction(allowNew) ? allowNew : allowNew && !isLoading,
emptyLabel: getEmptyLabel(),
isLoading,
minLength,
onInputChange: handleInputChange,
options: useCache && cachedQuery ? cachedQuery : options,
};
}
export function withAsync(Component) {
warn(false, 'Warning: `withAsync` is deprecated and will be removed in the next ' +
'major version. Use `useAsync` instead.');
const AsyncTypeahead = forwardRef((props, ref) => (React.createElement(Component, { ...props, ...useAsync(props), ref: ref })));
AsyncTypeahead.displayName = `withAsync(${getDisplayName(Component)})`;
AsyncTypeahead.propTypes = propTypes;
return AsyncTypeahead;
}