fix: Adds a loading message when needed in the Select component (#16531)

This commit is contained in:
Michael S. Molina 2021-09-02 13:17:47 -03:00 committed by GitHub
parent 2e11b05d73
commit 02798a3517
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 70 additions and 31 deletions

View File

@ -300,6 +300,7 @@ const USERS = [
]; ];
export const AsyncSelect = ({ export const AsyncSelect = ({
fetchOnlyOnSearch,
withError, withError,
withInitialValue, withInitialValue,
responseTime, responseTime,
@ -381,7 +382,9 @@ export const AsyncSelect = ({
> >
<Select <Select
{...rest} {...rest}
fetchOnlyOnSearch={fetchOnlyOnSearch}
options={withError ? fetchUserListError : fetchUserListPage} options={withError ? fetchUserListError : fetchUserListPage}
placeholder={fetchOnlyOnSearch ? 'Type anything' : 'Select...'}
value={ value={
withInitialValue withInitialValue
? { label: 'Valentina', value: 'Valentina' } ? { label: 'Valentina', value: 'Valentina' }

View File

@ -137,6 +137,13 @@ const StyledSpin = styled(Spin)`
margin-top: ${({ theme }) => -theme.gridUnit}px; margin-top: ${({ theme }) => -theme.gridUnit}px;
`; `;
const StyledLoadingText = styled.span`
${({ theme }) => `
margin-left: ${theme.gridUnit * 3}px;
color: ${theme.colors.grayscale.light1};
`}
`;
const MAX_TAG_COUNT = 4; const MAX_TAG_COUNT = 4;
const TOKEN_SEPARATORS = [',', '\n', '\t', ';']; const TOKEN_SEPARATORS = [',', '\n', '\t', ';'];
const DEBOUNCE_TIMEOUT = 500; const DEBOUNCE_TIMEOUT = 500;
@ -175,7 +182,8 @@ const Select = ({
); );
const [selectValue, setSelectValue] = useState(value); const [selectValue, setSelectValue] = useState(value);
const [searchedValue, setSearchedValue] = useState(''); const [searchedValue, setSearchedValue] = useState('');
const [isLoading, setLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [isTyping, setIsTyping] = useState(false);
const [error, setError] = useState(''); const [error, setError] = useState('');
const [isDropdownVisible, setIsDropdownVisible] = useState(false); const [isDropdownVisible, setIsDropdownVisible] = useState(false);
const [page, setPage] = useState(0); const [page, setPage] = useState(0);
@ -350,9 +358,10 @@ const Select = ({
const cachedCount = fetchedQueries.current.get(key); const cachedCount = fetchedQueries.current.get(key);
if (cachedCount) { if (cachedCount) {
setTotalCount(cachedCount); setTotalCount(cachedCount);
setIsTyping(false);
return; return;
} }
setLoading(true); setIsLoading(true);
const fetchOptions = options as OptionsPagePromise; const fetchOptions = options as OptionsPagePromise;
fetchOptions(value, page, pageSize) fetchOptions(value, page, pageSize)
.then(({ data, totalCount }: OptionsTypePage) => { .then(({ data, totalCount }: OptionsTypePage) => {
@ -361,39 +370,56 @@ const Select = ({
setTotalCount(totalCount); setTotalCount(totalCount);
}) })
.catch(onError) .catch(onError)
.finally(() => setLoading(false)); .finally(() => {
setIsLoading(false);
setIsTyping(false);
});
}, },
[options], [options],
); );
const handleOnSearch = debounce((search: string) => { const handleOnSearch = useMemo(
const searchValue = search.trim(); () =>
// enables option creation debounce((search: string) => {
if (allowNewOptions && isSingleMode) { const searchValue = search.trim();
const firstOption = selectOptions.length > 0 && selectOptions[0].value; // enables option creation
// replaces the last search value entered with the new one if (allowNewOptions && isSingleMode) {
// only when the value wasn't part of the original options const firstOption =
if ( selectOptions.length > 0 && selectOptions[0].value;
searchValue && // replaces the last search value entered with the new one
firstOption === searchedValue && // only when the value wasn't part of the original options
!initialOptions.find(o => o.value === searchedValue) if (
) { searchValue &&
selectOptions.shift(); firstOption === searchedValue &&
setSelectOptions(selectOptions); !initialOptions.find(o => o.value === searchedValue)
} ) {
if (searchValue && !hasOption(searchValue, selectOptions)) { selectOptions.shift();
const newOption = { setSelectOptions(selectOptions);
label: searchValue, }
value: searchValue, if (searchValue && !hasOption(searchValue, selectOptions)) {
}; const newOption = {
// adds a custom option label: searchValue,
const newOptions = [...selectOptions, newOption]; value: searchValue,
setSelectOptions(newOptions); };
setSelectValue(searchValue); // adds a custom option
} const newOptions = [...selectOptions, newOption];
} setSelectOptions(newOptions);
setSearchedValue(searchValue); setSelectValue(searchValue);
}, DEBOUNCE_TIMEOUT); }
}
setSearchedValue(searchValue);
if (!searchValue) {
setIsTyping(false);
}
}, DEBOUNCE_TIMEOUT),
[
allowNewOptions,
initialOptions,
isSingleMode,
searchedValue,
selectOptions,
],
);
const handlePagination = (e: UIEvent<HTMLElement>) => { const handlePagination = (e: UIEvent<HTMLElement>) => {
const vScroll = e.currentTarget; const vScroll = e.currentTarget;
@ -469,9 +495,18 @@ const Select = ({
if (!isDropdownVisible) { if (!isDropdownVisible) {
originNode.ref?.current?.scrollTo({ top: 0 }); originNode.ref?.current?.scrollTo({ top: 0 });
} }
if ((isLoading && selectOptions.length === 0) || isTyping) {
return <StyledLoadingText>{t('Loading...')}</StyledLoadingText>;
}
return error ? <Error error={error} /> : originNode; return error ? <Error error={error} /> : originNode;
}; };
const onInputKeyDown = () => {
if (isAsync && !isTyping) {
setIsTyping(true);
}
};
const SuffixIcon = () => { const SuffixIcon = () => {
if (isLoading) { if (isLoading) {
return <StyledSpin size="small" />; return <StyledSpin size="small" />;
@ -496,6 +531,7 @@ const Select = ({
mode={mappedMode} mode={mappedMode}
onDeselect={handleOnDeselect} onDeselect={handleOnDeselect}
onDropdownVisibleChange={handleOnDropdownVisibleChange} onDropdownVisibleChange={handleOnDropdownVisibleChange}
onInputKeyDown={onInputKeyDown}
onPopupScroll={isAsync ? handlePagination : undefined} onPopupScroll={isAsync ? handlePagination : undefined}
onSearch={shouldShowSearch ? handleOnSearch : undefined} onSearch={shouldShowSearch ? handleOnSearch : undefined}
onSelect={handleOnSelect} onSelect={handleOnSelect}