From b246d602922cd559c6390b4c0d8f00bc1e687b52 Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Sat, 5 Oct 2024 15:59:58 +0700 Subject: [PATCH 1/2] Homework - Implement input feature --- src/components/Input/index.tsx | 67 ++++++++++++++++++++++++++++++--- src/components/Input/input.scss | 49 ++++++++++++++++++++++-- 2 files changed, 108 insertions(+), 8 deletions(-) diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx index 453c742..4de039f 100644 --- a/src/components/Input/index.tsx +++ b/src/components/Input/index.tsx @@ -2,6 +2,7 @@ import "./input.scss"; import { fetchData } from "../../utils/fetch-data"; import { debounce } from "../../utils/deboucne"; import Loader from "../Loader"; +import { ChangeEvent, useEffect, useState } from "react"; export interface InputProps { /** Placeholder of the input */ @@ -10,14 +11,70 @@ export interface InputProps { onSelectItem: (item: string) => void; } +const DEBOUNCE_TIME = 300; + const Input = ({ placeholder, onSelectItem }: InputProps) => { + const [searchText, setSearchText] = useState(''); + const [isFetching, setIsFetching] = useState(false); + const [searchItems, setSearchItems] = useState([]); + const [errorMsg, setErrorMsg] = useState(''); + // DO NOT remove this log - console.log('input re-render') + console.log("input re-render"); + + useEffect(() => { + let ignore = false; + setIsFetching(true); + setErrorMsg(''); + fetchData(searchText).then((response) => { + if (!ignore) { + setSearchItems(response || []); + } + }).catch((error) => { + if (!ignore) { + setErrorMsg(error); + } + }).finally(() => { + if (!ignore) { + setIsFetching(false); + } + }); + return () => { + ignore = true; + } + }, [searchText]) - // Your code start here - return - // Your code end here + const handleOnChange = debounce((e: ChangeEvent) => { + const searchValue = e.target.value; + setSearchText(searchValue); + }, DEBOUNCE_TIME); + + const renderSearchResults = () => { + if (isFetching) { + return ; + } else if (errorMsg) { + return
{errorMsg}
; + } else if (!searchItems.length) { + return
No result
+ } else { + return ( +
+ {searchItems.map((item, index) => ( +
onSelectItem(item)}>{item}
+ ))} +
+ ); + } + }; + + return ( +
+ + {(!searchText.length || searchText.trim() === '') ? "" : ( +
{renderSearchResults()}
+ )} +
+ ); }; export default Input; - diff --git a/src/components/Input/input.scss b/src/components/Input/input.scss index 1dafbe7..69773f3 100644 --- a/src/components/Input/input.scss +++ b/src/components/Input/input.scss @@ -1,6 +1,49 @@ * { - box-sizing: border-box; + box-sizing: border-box; } -html{ - font-size: 16px; +html { + font-size: 16px; +} +.search { + &__wrapper { + width: 300px; + } + &__input { + padding: 12px 16px; + font-size: 16px; + border-radius: 4px; + width: 100%; + } + &__result { + width: 100%; + position: relative; + border: solid 1px #ddd; + border-radius: 4px; + margin-top: 4px; + min-height: 100px; + max-height: 300px; + overflow: auto; + } + &__items { + display: flex; + flex-direction: column + } + &__item { + padding: 16px; + cursor: pointer; + &:hover { + background-color: #eee; + } + } + &__no-result { + padding: 12px 16px; + text-align: center; + font-style: italic; + color: #333; + } + &__error-msg { + padding: 12px 16px; + font-style: italic; + color: red; + } } From ca27eac2460cc3742bd96d20dd7509d33d860586 Mon Sep 17 00:00:00 2001 From: Duy Nguyen Date: Sat, 5 Oct 2024 16:02:32 +0700 Subject: [PATCH 2/2] format code --- src/components/Input/input.scss | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/components/Input/input.scss b/src/components/Input/input.scss index 69773f3..7d75173 100644 --- a/src/components/Input/input.scss +++ b/src/components/Input/input.scss @@ -5,28 +5,28 @@ html { font-size: 16px; } .search { - &__wrapper { - width: 300px; - } + &__wrapper { + width: 300px; + } &__input { padding: 12px 16px; - font-size: 16px; + font-size: 16px; border-radius: 4px; width: 100%; } &__result { - width: 100%; - position: relative; - border: solid 1px #ddd; + width: 100%; + position: relative; + border: solid 1px #ddd; border-radius: 4px; margin-top: 4px; min-height: 100px; max-height: 300px; overflow: auto; } - &__items { + &__items { display: flex; - flex-direction: column + flex-direction: column; } &__item { padding: 16px; @@ -36,14 +36,14 @@ html { } } &__no-result { - padding: 12px 16px; + padding: 12px 16px; text-align: center; - font-style: italic; + font-style: italic; color: #333; } &__error-msg { padding: 12px 16px; - font-style: italic; + font-style: italic; color: red; } }