From a4f9219f80e13c60637098c6729cfe7e2e57ddeb Mon Sep 17 00:00:00 2001 From: "kien.nguyen3" Date: Fri, 4 Oct 2024 17:51:52 +0700 Subject: [PATCH 1/2] add input event and state --- src/components/Input/index.tsx | 66 ++++++++++++++++++++++++++++++++- src/components/Input/input.scss | 27 ++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx index 453c742..e9e7431 100644 --- a/src/components/Input/index.tsx +++ b/src/components/Input/index.tsx @@ -1,7 +1,9 @@ import "./input.scss"; +import { useCallback, useState } from "react"; import { fetchData } from "../../utils/fetch-data"; import { debounce } from "../../utils/deboucne"; import Loader from "../Loader"; +import { log } from "../../utils/log"; export interface InputProps { /** Placeholder of the input */ @@ -15,7 +17,69 @@ const Input = ({ placeholder, onSelectItem }: InputProps) => { console.log('input re-render') // Your code start here - return + const [initital, setInitital] = useState(true); + const [fetching, setFetching] = useState(false); + const [success, setSuccess] = useState(false); + const [error, setError] = useState(""); + const [listResults, setListResults] = useState([]); + + const handleSearch = useCallback(async (query: any) => { + setInitital(false); + setFetching(true); + try { + await fetchData(query) + .then(resp => { + setError(""); + setFetching(false); + console.log(resp); + setListResults(resp); + + }) + } catch (err) { + setError(err as string); + setFetching(false); + } + }, []); + + const onInputSearch = useCallback((query: any) => { + debounce( + handleSearch.bind(handleSearch(query)), + 100 + ); + }, []) + + const renderHTML = () => { + if(fetching) { + return + } else if(error) { + return

{error}

+ } else { + const htmlResult = listResults.length ? + : +
No Results
; + return htmlResult; + } + } + + return ( + <> + onInputSearch((evt.target as HTMLTextAreaElement).value)}> +
+ {!initital ? renderHTML(): ""} +
+ + ); // Your code end here }; diff --git a/src/components/Input/input.scss b/src/components/Input/input.scss index 1dafbe7..2a96654 100644 --- a/src/components/Input/input.scss +++ b/src/components/Input/input.scss @@ -4,3 +4,30 @@ html{ font-size: 16px; } + +input { + font-size: 16px; + padding: 8px; + border: 1px solid grey; + border-radius: 10px; +} + +.text-error { + color: red; +} + +.search-result { + margin-top: 16px; +} + +.result-list { + text-align: left; + padding-left: 0; + list-style: none; + + li { + &:hover { + cursor: pointer; + } + } +} From bb19adcf130f502daaa61cfc14c648abbc8ce081 Mon Sep 17 00:00:00 2001 From: Kien Nguyen Date: Sat, 5 Oct 2024 11:20:18 +0700 Subject: [PATCH 2/2] update race condition --- src/components/Input/index.tsx | 52 ++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx index e9e7431..37e0744 100644 --- a/src/components/Input/index.tsx +++ b/src/components/Input/index.tsx @@ -1,9 +1,8 @@ import "./input.scss"; -import { useCallback, useState } from "react"; +import { useCallback, useState, useEffect } from "react"; import { fetchData } from "../../utils/fetch-data"; import { debounce } from "../../utils/deboucne"; import Loader from "../Loader"; -import { log } from "../../utils/log"; export interface InputProps { /** Placeholder of the input */ @@ -19,34 +18,37 @@ const Input = ({ placeholder, onSelectItem }: InputProps) => { // Your code start here const [initital, setInitital] = useState(true); const [fetching, setFetching] = useState(false); - const [success, setSuccess] = useState(false); + const [query, setQuery] = useState(null); const [error, setError] = useState(""); const [listResults, setListResults] = useState([]); - const handleSearch = useCallback(async (query: any) => { - setInitital(false); - setFetching(true); - try { - await fetchData(query) - .then(resp => { - setError(""); + useEffect(() => { + let isMounted = true; + if(query != null) { + setInitital(false); + setFetching(true); + fetchData(query) + .then(resp => { + if(isMounted) { + setError(""); + setFetching(false); + console.log(resp); + setListResults(resp); + } + }) + .catch ((err) => { + setError(err as string); setFetching(false); - console.log(resp); - setListResults(resp); - - }) - } catch (err) { - setError(err as string); - setFetching(false); + }) } - }, []); + return () => { + isMounted = false; // Prevent state updates on unmounted component + }; + }, [query]); - const onInputSearch = useCallback((query: any) => { - debounce( - handleSearch.bind(handleSearch(query)), - 100 - ); - }, []) + const onInputSearch = useCallback(debounce((query: any) => { + setQuery(query); + }, 100), []) const renderHTML = () => { if(fetching) { @@ -57,7 +59,7 @@ const Input = ({ placeholder, onSelectItem }: InputProps) => { const htmlResult = listResults.length ?
    { - listResults.map((item, index) => + listResults.map((item) =>
  • onSelectItem(item)}