diff --git a/.gitignore b/.gitignore index 87b58f0..5eea626 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ storybook-static *.njsproj *.sln *.sw? +*.history +.vercel diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx index 453c742..9709187 100644 --- a/src/components/Input/index.tsx +++ b/src/components/Input/index.tsx @@ -1,7 +1,8 @@ import "./input.scss"; import { fetchData } from "../../utils/fetch-data"; import { debounce } from "../../utils/deboucne"; -import Loader from "../Loader"; +import { ChangeEvent, useCallback, useRef, useState } from "react"; +import { FETCHING_STATUS } from "../../utils/constants"; export interface InputProps { /** Placeholder of the input */ @@ -10,12 +11,110 @@ export interface InputProps { onSelectItem: (item: string) => void; } +const SearchResultList: React.FC<{ + list: string[], + status: string | null, + onSelectItem: (item: string) => void}> = ({ + list, + status, + onSelectItem + }) => { + if (status === FETCHING_STATUS.FETCHING) { + return

Loading...

; + } + + if (status === FETCHING_STATUS.ERROR) { + return

Occur an error.

+ } + + if (!list.length && status !== FETCHING_STATUS.INITIAL) { + return

No results.

+ } + + if (list.length) { + return + } + + return null; +} + const Input = ({ placeholder, onSelectItem }: InputProps) => { // DO NOT remove this log - console.log('input re-render') - + console.log('input re-render'); // Your code start here - return + const [status, setStatus] = useState(FETCHING_STATUS.INITIAL); + const [list, setList] = useState([]); + const inputRef = useRef(); + const fetchDataPromise = useRef>(); + + const _onChangeFnc = (e:ChangeEvent) => { + const { value } = e.target; + + // Return when there is empty value. + if(!value.trim()) { + setList([]); + setStatus(FETCHING_STATUS.INITIAL); + + return; + } + + inputRef.current = value; + fetchDataPromise.current = undefined; + + setStatus(FETCHING_STATUS.FETCHING); + + // Fetch API. + const _fetchDataPromise = fetchData(value); + fetchDataPromise.current = _fetchDataPromise; + + _fetchDataPromise + .then((res) => { + if (isCheckXhr(_fetchDataPromise)) { + setStatus(FETCHING_STATUS.INITIAL); + return; + } + setList(res); + setStatus(FETCHING_STATUS.SUCCESS); + }) + .catch(() => { + if (isCheckXhr(_fetchDataPromise)) { + setStatus(FETCHING_STATUS.INITIAL); + return; + } + setList([]); + setStatus(FETCHING_STATUS.ERROR); + }) + .finally(() => { + fetchDataPromise.current = undefined; + }); + } + + const isCheckXhr = (promise: Promise) => { + // Skip previous request. + return fetchDataPromise.current != promise; + } + + const isChangeInputRef = (e: string) => { + const isChanged = e !== inputRef.current; + + return isChanged; + } + + const _onChangeDebounce = useCallback( + debounce(_onChangeFnc, 300), + [] + ); + + return
+ + +
// Your code end here }; diff --git a/src/components/Input/input.scss b/src/components/Input/input.scss index 1dafbe7..3b235ad 100644 --- a/src/components/Input/input.scss +++ b/src/components/Input/input.scss @@ -4,3 +4,25 @@ html{ font-size: 16px; } + +.search-input { + &--text { + padding: 5px 10px; + } + &--list { + list-style: none; + padding: 0; + margin: 0; + border: 1px solid #000; + border-top: none; + max-height: 350px; + overflow-y: auto; + &--item { + padding: 10px; + cursor: pointer; + &:hover { + background-color: antiquewhite; + } + } + } +} \ No newline at end of file diff --git a/src/utils/constants.ts b/src/utils/constants.ts new file mode 100644 index 0000000..1078f4d --- /dev/null +++ b/src/utils/constants.ts @@ -0,0 +1,6 @@ +export const FETCHING_STATUS = { + "INITIAL": "initial", + "FETCHING": "fetching", + "ERROR": "error", + "SUCCESS": "success" +} \ No newline at end of file