UNPKG

react-bootstrap-typeahead

Version:
151 lines (147 loc) 6.34 kB
import _extends from "@babel/runtime/helpers/extends"; import _defineProperty from "@babel/runtime/helpers/defineProperty"; import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties"; var _excluded = ["allowNew", "delay", "emptyLabel", "isLoading", "minLength", "onInputChange", "onSearch", "options", "promptText", "searchText", "useCache"]; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } 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'; var propTypes = { /** * Delay, in milliseconds, before performing search. */ delay: PropTypes.number, /** * Whether or not a request is currently pending. Necessary for the * container to know when new results are available. */ isLoading: PropTypes.bool.isRequired, /** * Number of input characters that must be entered before showing results. */ minLength: PropTypes.number, /** * Callback to perform when the search is executed. */ onSearch: PropTypes.func.isRequired, /** * Options to be passed to the typeahead. Will typically be the query * results, but can also be initial default options. */ options: PropTypes.arrayOf(optionType), /** * Message displayed in the menu when there is no user input. */ promptText: PropTypes.node, /** * Message displayed in the menu while the request is pending. */ searchText: PropTypes.node, /** * Whether or not the component should cache query results. */ useCache: PropTypes.bool }; /** * Logic that encapsulates common behavior and functionality around * asynchronous searches, including: * * - Debouncing user input * - Optional query caching * - Search prompt and empty results behaviors */ export function useAsync(props) { var allowNew = props.allowNew, _props$delay = props.delay, delay = _props$delay === void 0 ? 200 : _props$delay, emptyLabel = props.emptyLabel, isLoading = props.isLoading, _props$minLength = props.minLength, minLength = _props$minLength === void 0 ? 2 : _props$minLength, onInputChange = props.onInputChange, onSearch = props.onSearch, _props$options = props.options, options = _props$options === void 0 ? [] : _props$options, _props$promptText = props.promptText, promptText = _props$promptText === void 0 ? 'Type to search...' : _props$promptText, _props$searchText = props.searchText, searchText = _props$searchText === void 0 ? 'Searching...' : _props$searchText, _props$useCache = props.useCache, useCache = _props$useCache === void 0 ? true : _props$useCache, otherProps = _objectWithoutProperties(props, _excluded); var cacheRef = useRef({}); var handleSearchDebouncedRef = useRef(null); var queryRef = useRef(props.defaultInputValue || ''); var forceUpdate = useForceUpdate(); var prevProps = usePrevious(props); var handleSearch = useCallback(function (query) { queryRef.current = query; if (!query || minLength && query.length < minLength) { return; } // Use cached results, if applicable. if (useCache && cacheRef.current[query]) { // Re-render the component with the cached results. forceUpdate(); return; } // Perform the search. onSearch(query); }, [forceUpdate, minLength, onSearch, useCache]); // Set the debounced search function. useEffect(function () { handleSearchDebouncedRef.current = debounce(handleSearch, delay); return function () { handleSearchDebouncedRef.current && handleSearchDebouncedRef.current.cancel(); }; }, [delay, handleSearch]); useEffect(function () { // Ensure that we've gone from a loading to a completed state. Otherwise // an empty response could get cached if the component updates during the // request (eg: if the parent re-renders for some reason). if (!isLoading && prevProps && prevProps.isLoading && useCache) { cacheRef.current[queryRef.current] = options; } }); var getEmptyLabel = function getEmptyLabel() { if (!queryRef.current.length) { return promptText; } if (isLoading) { return searchText; } return emptyLabel; }; var handleInputChange = useCallback(function (query, e) { onInputChange && onInputChange(query, e); handleSearchDebouncedRef.current && handleSearchDebouncedRef.current(query); }, [onInputChange]); var cachedQuery = cacheRef.current[queryRef.current]; return _objectSpread(_objectSpread({}, otherProps), {}, { // Disable custom selections during a search if `allowNew` isn't a function. allowNew: isFunction(allowNew) ? allowNew : allowNew && !isLoading, emptyLabel: getEmptyLabel(), isLoading: isLoading, minLength: minLength, onInputChange: handleInputChange, options: useCache && cachedQuery ? cachedQuery : options }); } /* istanbul ignore next */ export function withAsync(Component) { warn(false, 'Warning: `withAsync` is deprecated and will be removed in the next ' + 'major version. Use `useAsync` instead.'); var AsyncTypeahead = /*#__PURE__*/forwardRef(function (props, ref) { return /*#__PURE__*/React.createElement(Component, _extends({}, props, useAsync(props), { ref: ref })); }); AsyncTypeahead.displayName = "withAsync(".concat(getDisplayName(Component), ")"); // @ts-ignore AsyncTypeahead.propTypes = propTypes; return AsyncTypeahead; }