1
0
Fork 1
mirror of https://github.com/SomboChea/ui synced 2024-05-12 22:41:37 +07:00
verdaccio-ui/src/components/Search/Search.tsx

156 lines
4.6 KiB
TypeScript
Raw Normal View History

import React, { useState, FormEvent, useCallback } from 'react';
2019-02-03 17:23:33 +07:00
import debounce from 'lodash/debounce';
import { RouteComponentProps, withRouter } from 'react-router';
import { SuggestionSelectedEventData } from 'react-autosuggest';
2019-02-03 17:23:33 +07:00
import AutoComplete from '../AutoComplete';
import { callSearch } from '../../utils/calls';
2019-02-03 17:23:33 +07:00
import SearchAdornment from './SearchAdornment';
2019-02-03 17:23:33 +07:00
const CONSTANTS = {
API_DELAY: 300,
PLACEHOLDER_TEXT: 'Search Packages',
ABORT_ERROR: 'AbortError',
};
const Search: React.FC<RouteComponentProps> = ({ history }) => {
const [suggestions, setSuggestions] = useState([]);
const [loaded, setLoaded] = useState(false);
const [search, setSearch] = useState('');
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const [requestList, setRequestList] = useState<Array<{ abort: () => void }>>([]);
2019-06-30 05:39:56 +07:00
2019-02-03 17:23:33 +07:00
/**
* Cancel all the requests which are in pending state.
*/
const cancelAllSearchRequests = useCallback(() => {
requestList.forEach(request => request.abort());
setRequestList([]);
}, [requestList, setRequestList]);
2019-02-03 17:23:33 +07:00
/**
* As user focuses out from input, we cancel all the request from requestList
* and set the API state parameters to default boolean values.
2019-02-03 17:23:33 +07:00
*/
const handleOnBlur = useCallback(
(event: FormEvent<HTMLInputElement>) => {
// stops event bubbling
event.stopPropagation();
setLoaded(false);
setLoading(false);
setError(false);
cancelAllSearchRequests();
},
[setLoaded, setLoading, cancelAllSearchRequests, setError]
);
2019-02-03 17:23:33 +07:00
/**
* onChange method for the input element.
*/
const handleSearch = useCallback(
(event: FormEvent<HTMLInputElement>, { newValue, method }) => {
// stops event bubbling
event.stopPropagation();
if (method === 'type') {
const value = newValue.trim();
setLoading(true);
setError(false);
setSearch(value);
setLoaded(false);
/**
* A use case where User keeps adding and removing value in input field,
* so we cancel all the existing requests when input is empty.
*/
if (value.length === 0) {
cancelAllSearchRequests();
2019-02-03 17:23:33 +07:00
}
}
},
[cancelAllSearchRequests]
);
2019-02-03 17:23:33 +07:00
/**
* Cancel all the request from list and make request list empty.
2019-02-03 17:23:33 +07:00
*/
const handlePackagesClearRequested = useCallback(() => {
setSuggestions([]);
}, [setSuggestions]);
2019-02-03 17:23:33 +07:00
/**
* When an user select any package by clicking or pressing return key.
2019-02-03 17:23:33 +07:00
*/
const handleClickSearch = useCallback(
(
event: FormEvent<HTMLInputElement>,
{ suggestionValue, method }: SuggestionSelectedEventData<unknown>
): void | undefined => {
// stops event bubbling
event.stopPropagation();
switch (method) {
case 'click':
case 'enter':
setSearch('');
history.push(`/-/web/detail/${suggestionValue}`);
break;
2019-02-03 17:23:33 +07:00
}
},
[history]
);
feat: migrating flow to typescript (#47) This PR convert the code base to Typescript, the changes are the following: - migrate code base to Typescript (3.4.x) - enable `eslint` and `@typescript-eslint/eslint-plugin` (warnings still need to be addressed in future pull request - update relevant dependencies for this PR (linting, etc) - enable `bundlezise` (it was disabled for some reason) * refactor: refactoring to typescript * refactor: migrating to typescript * refactor: applied feedbacks * fix: fixed conflicts * refactored: changed registry * refactor: updated registry & removed unnecessary lib * fix: fixed registry ur * fix: fixed page load * refactor: refactored footer wip * refactor: converting to ts..wip * refactor: converting to ts. wip * refactor: converting to ts. wip * refactor: converting to ts * refactor: converting to ts * fix: fixed load errors * refactor: converted files to ts * refactor: removed flow from tests * fix: removed transpiled files * refactor: added ts-ignore * fix: fixed errors * fix: fixed types * fix: fixing jest import -.- * fix: fixing lint errors * fix: fixing lint errors * fix: fixed lint errors * refactor: removed unnecessary tsconfig's config * fix: fixing errors * fix: fixed warning * fix: fixed test * refactor: wip * refactor: wip * refactor: wip * fix: fixing tests: wip * wip * wip * fix: fixed search test * wip * fix: fixing lint errors * fix: re-added stylelint * refactor: updated stylelint script * fix: fixed: 'styles.js' were found. * fix: fixed Search tests * chore: enable eslint eslint needs expecitely to know which file has to lint, by default is JS, in this case we need also ts,tsx files eslint . --ext .js,.ts * chore: vcode eslint settings * chore: restore eslint previous conf * chore: clean jest config * chore: fix eslint warnings * chore: eslint errors cleared chore: clean warnings chore: remove github actions test phases chore: remove dupe rule * chore: update handler name * chore: restore logo from img to url css prop - loading images with css is more performant than using img html tags, switching this might be a breaking change - restore no-empty-source seems the linting do not accept false - update snapshots - remove @material-ui/styles * chore: update stylelint linting * chore: update stylelint linting * chore: fix a mistake on move tabs to a function * chore: eanble bundlezie * chore: use default_executor in circleci * chore: update readme
2019-06-20 19:37:28 +07:00
2019-02-03 17:23:33 +07:00
/**
* Fetch packages from API.
* For AbortController see: https://developer.mozilla.org/en-US/docs/Web/API/AbortController
2019-02-03 17:23:33 +07:00
*/
const handleFetchPackages = useCallback(
async ({ value }: { value: string }) => {
try {
const controller = new window.AbortController();
const signal = controller.signal;
// Keep track of search requests.
setRequestList([...requestList, controller]);
const suggestions = await callSearch(value, signal);
// @ts-ignore FIXME: Argument of type 'unknown' is not assignable to parameter of type 'SetStateAction<never[]>'
setSuggestions(suggestions);
setLoaded(true);
} catch (error) {
/**
* AbortError is not the API error.
* It means browser has cancelled the API request.
*/
if (error.name === CONSTANTS.ABORT_ERROR) {
setError(false);
setLoaded(false);
} else {
setError(true);
setLoaded(false);
}
} finally {
setLoading(false);
}
},
[requestList, setRequestList, setSuggestions, setLoaded, setError, setLoading]
);
return (
<AutoComplete
onBlur={handleOnBlur}
onChange={handleSearch}
onCleanSuggestions={handlePackagesClearRequested}
onClick={handleClickSearch}
onSuggestionsFetch={debounce(handleFetchPackages, CONSTANTS.API_DELAY)}
placeholder={CONSTANTS.PLACEHOLDER_TEXT}
startAdornment={<SearchAdornment />}
suggestions={suggestions}
suggestionsError={error}
suggestionsLoaded={loaded}
suggestionsLoading={loading}
value={search}
/>
);
};
2019-02-03 17:23:33 +07:00
export default withRouter(Search);