UNPKG

react-bootstrap-typeahead

Version:
81 lines (80 loc) 3.23 kB
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; }