From 1ce2f774100ffdda21f9765a88bcdc959302a3a6 Mon Sep 17 00:00:00 2001 From: "tuan.hoang4" Date: Mon, 7 Oct 2024 17:27:54 +0700 Subject: [PATCH] update Input component --- src/App.tsx | 42 +++++------------- src/components/Input/index.tsx | 49 +++++++++++++++++---- src/components/Input/input.scss | 72 ++++++++++++++++++++++++++++++- src/components/Input/useInput.tsx | 40 +++++++++++++++++ 4 files changed, 163 insertions(+), 40 deletions(-) create mode 100644 src/components/Input/useInput.tsx diff --git a/src/App.tsx b/src/App.tsx index afe48ac..31ac718 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,35 +1,15 @@ -import { useState } from 'react' -import reactLogo from './assets/react.svg' -import viteLogo from '/vite.svg' -import './App.css' +import "./App.css"; +import Input from "./components/Input/index.tsx"; function App() { - const [count, setCount] = useState(0) - - return ( - <> -
- - Vite logo - - - React logo - -
-

Vite + React

-
- -

- Edit src/App.tsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

- - ) + const handleSelectItem = (item: string) => { + console.log(`Selected item: ${item}`); + }; + return ( + <> + + + ); } -export default App +export default App; diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx index 453c742..10b20b9 100644 --- a/src/components/Input/index.tsx +++ b/src/components/Input/index.tsx @@ -1,7 +1,6 @@ import "./input.scss"; -import { fetchData } from "../../utils/fetch-data"; -import { debounce } from "../../utils/deboucne"; import Loader from "../Loader"; +import { useInput } from "./useInput"; export interface InputProps { /** Placeholder of the input */ @@ -12,12 +11,46 @@ export interface InputProps { 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 - // Your code end here -}; + const { loading, error, result, onChange } = useInput(); + + const renderResults = () => { + if (!Array.isArray(result)) return null; + + if (result.length > 0) { + return ( + + ); + } + return

No results

; + }; -export default Input; + const renderContent = () => { + if (loading) return ; + if (error) return

{error}

; + return renderResults(); + }; + + return ( +
+ + {renderContent()} +
+ ); +}; +export default Input; \ No newline at end of file diff --git a/src/components/Input/input.scss b/src/components/Input/input.scss index 1dafbe7..1f8a803 100644 --- a/src/components/Input/input.scss +++ b/src/components/Input/input.scss @@ -1,6 +1,76 @@ * { box-sizing: border-box; } -html{ + +html { font-size: 16px; } + +.main { + width: 100%; + max-width: 600px; + margin: 0 auto; + padding: 20px; + border: 1px solid #e0e0e0; + border-radius: 8px; + background-color: #f9f9f9; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +input { + padding: 10px 15px; + border: 2px solid #007BFF; + border-radius: 5px; + background-color: #ffffff; + color: #333; + width: 100%; + height: 45px; + transition: border-color 0.3s; + + &:focus { + border-color: #0056b3; + outline: none; + } +} + +.error { + color: #dc3545; + padding: 10px; + border: 1px solid currentColor; + background-color: rgba(220, 53, 69, 0.1); + border-radius: 5px; +} + +.list { + display: flex; + flex-direction: column; + padding: 0; + list-style: none; + margin-top: 10px; +} + +.item { + padding: 12px 15px; + border: 1px solid transparent; + background-color: #ffffff; + color: #333; + cursor: pointer; + transition: background-color 0.2s, border-color 0.2s; + + &:hover { + background-color: #f1f1f1; + border-color: #007BFF; + } + + &:last-child { + border-bottom: none; + } +} + +.no-results { + color: #6c757d; + text-align: center; + padding: 10px; + background-color: #f9f9f9; + border-radius: 5px; +} \ No newline at end of file diff --git a/src/components/Input/useInput.tsx b/src/components/Input/useInput.tsx new file mode 100644 index 0000000..784d496 --- /dev/null +++ b/src/components/Input/useInput.tsx @@ -0,0 +1,40 @@ +import { ChangeEvent, useCallback, useRef, useState } from "react"; +import { debounce } from "../../utils/deboucne"; +import { fetchData } from "../../utils/fetch-data"; +export const useInput = () => { + const [ loading, setLoading ] = useState(false); + const [ error, setError ] = useState(); + const [ result, setResult ] = useState(); + const lastPromise = useRef>(); + + const ignoreRequest = (promise: Promise) => { + return lastPromise.current !== promise; + } + const handleResponse = useCallback((promise: Promise, callback: () => void) => { + if(ignoreRequest(promise)) return; + callback(); + setLoading(false); + lastPromise.current = undefined; + }, [lastPromise]); + const onChange = useCallback(debounce(async (e: ChangeEvent) => { + const value = (e.target as HTMLInputElement).value; + setLoading(true); + setError(""); + setResult([]); + let promise = fetchData(value); + lastPromise.current = promise; + promise + .then(data => { + handleResponse(promise, () => setResult(data)); + }) + .catch(err => { + handleResponse(promise, () => setError((err as string))); + }) + }, 100), + [] + ); + return { + loading, error, result, + onChange + } +} \ No newline at end of file