Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 11 additions & 31 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
)
const handleSelectItem = (item: string) => {
console.log(`Selected item: ${item}`);
};
return (
<>
<Input onSelectItem={handleSelectItem} />
</>
);
}

export default App
export default App;
49 changes: 41 additions & 8 deletions src/components/Input/index.tsx
Original file line number Diff line number Diff line change
@@ -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 */
Expand All @@ -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 <input></input>
// Your code end here
};
const { loading, error, result, onChange } = useInput();

const renderResults = () => {
if (!Array.isArray(result)) return null;

if (result.length > 0) {
return (
<ul className="list">
{result.map(item => (
<li
key={item}
className="item"
onClick={() => onSelectItem(item)}
>
{item}
</li>
))}
</ul>
);
}
return <p className="no-results">No results</p>;
};

export default Input;
const renderContent = () => {
if (loading) return <Loader />;
if (error) return <p className="error">{error}</p>;
return renderResults();
};

return (
<div className="main">
<input
placeholder={placeholder}
onChange={onChange}
/>
{renderContent()}
</div>
);
};

export default Input;
72 changes: 71 additions & 1 deletion src/components/Input/input.scss
Original file line number Diff line number Diff line change
@@ -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;
}
40 changes: 40 additions & 0 deletions src/components/Input/useInput.tsx
Original file line number Diff line number Diff line change
@@ -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<boolean>(false);
const [ error, setError ] = useState<string>();
const [ result, setResult ] = useState<string[]>();
const lastPromise = useRef<Promise<string[]>>();

const ignoreRequest = (promise: Promise<string[]>) => {
return lastPromise.current !== promise;
}
const handleResponse = useCallback((promise: Promise<string[]>, 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
}
}