From b82f5debddee5a90d4b0ddbeab295220ff8f4c7d Mon Sep 17 00:00:00 2001 From: anastasiiadns Date: Fri, 17 Apr 2026 13:26:06 +0300 Subject: [PATCH 1/3] add task solution --- README.md | 2 +- package-lock.json | 12 +++--- package.json | 2 +- src/App.tsx | 106 ++++++++++++++++++++++++++++------------------ src/index.tsx | 5 ++- 5 files changed, 78 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index b8897503d..7a8bac007 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ that will suggest people matching an entered text. - Install Prettier Extention and use this [VSCode settings](https://mate-academy.github.io/fe-program/tools/vscode/settings.json) to enable format on save. - Implement a solution following the [React task guideline](https://github.com/mate-academy/react_task-guideline#react-tasks-guideline). - Use the [React TypeScript cheat sheet](https://mate-academy.github.io/fe-program/js/extra/react-typescript). -- Replace `` with your Github username in the [DEMO LINK](https://.github.io/react_autocomplete/) and add it to the PR description. +- Replace `` with your Github username in the [DEMO LINK](https://anastasiiadns.github.io/react_autocomplete/) and add it to the PR description. - Don't remove the `data-qa` attributes. It is required for tests. ## Troubleshooting diff --git a/package-lock.json b/package-lock.json index a57a24cb7..62b57e2f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "devDependencies": { "@cypress/react18": "^2.0.1", "@faker-js/faker": "^8.4.1", - "@mate-academy/scripts": "^1.8.5", + "@mate-academy/scripts": "^2.1.3", "@mate-academy/students-ts-config": "*", "@mate-academy/stylelint-config": "*", "@types/lodash.debounce": "^4.0.9", @@ -1189,10 +1189,11 @@ } }, "node_modules/@mate-academy/scripts": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-1.8.5.tgz", - "integrity": "sha512-mHRY2FkuoYCf5U0ahIukkaRo5LSZsxrTSgMJheFoyf3VXsTvfM9OfWcZIDIDB521kdPrScHHnRp+JRNjCfUO5A==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-2.1.3.tgz", + "integrity": "sha512-a07wHTj/1QUK2Aac5zHad+sGw4rIvcNl5lJmJpAD7OxeSbnCdyI6RXUHwXhjF5MaVo9YHrJ0xVahyERS2IIyBQ==", "dev": true, + "license": "MIT", "dependencies": { "@octokit/rest": "^17.11.2", "@types/get-port": "^4.2.0", @@ -6800,7 +6801,8 @@ "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" }, "node_modules/lodash.get": { "version": "4.4.2", diff --git a/package.json b/package.json index cda47c54b..2ae5e8de2 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "devDependencies": { "@cypress/react18": "^2.0.1", "@faker-js/faker": "^8.4.1", - "@mate-academy/scripts": "^1.8.5", + "@mate-academy/scripts": "^2.1.3", "@mate-academy/students-ts-config": "*", "@mate-academy/stylelint-config": "*", "@types/lodash.debounce": "^4.0.9", diff --git a/src/App.tsx b/src/App.tsx index a88cd7a6d..b49cbf685 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,15 +1,39 @@ -import React from 'react'; import './App.scss'; -import { peopleFromServer } from './data/people'; +import { Person } from './types/Person'; +import React, { useCallback, useState } from 'react'; +import debounce from 'lodash.debounce'; -export const App: React.FC = () => { - const { name, born, died } = peopleFromServer[0]; +type Props = { + persons: Person[]; + delay?: number; + onSelected?: (person: Person) => void; +}; + +export const App: React.FC = ({ persons, delay = 300, onSelected }) => { + const [query, setQuery] = useState(''); + const [appliedQuery, setAppliedQuery] = useState(''); + const [selectedPerson, setSelectedPerson] = useState(null); + const [isFocused, setIsFocused] = useState(false); + + const applyQuery = useCallback(debounce(setAppliedQuery, delay), [delay]); + + const handleQueryChange = (event: React.ChangeEvent) => { + setQuery(event.target.value); + setSelectedPerson(null); + applyQuery(event.target.value); + }; + + const filteredPeople = persons.filter(person => + person.name.toLowerCase().includes(appliedQuery.trim().toLowerCase()), + ); return (

- {`${name} (${born} - ${died})`} + {selectedPerson + ? `${selectedPerson.name} (${selectedPerson.born} - ${selectedPerson.died})` + : 'No selected person'}

@@ -19,55 +43,55 @@ export const App: React.FC = () => { placeholder="Enter a part of the name" className="input" data-cy="search-input" + value={query} + onChange={handleQueryChange} + onFocus={() => setIsFocused(true)} + onBlur={() => setIsFocused(false)} />
-
-
-
-

Pieter Haverbeke

-
- -
-

Pieter Bernard Haverbeke

-
- -
-

Pieter Antone Haverbeke

-
- -
-

Elisabeth Haverbeke

-
- -
-

Pieter de Decker

-
- -
-

Petronella de Decker

-
- -
-

Elisabeth Hercke

+ {isFocused && filteredPeople.length > 0 && ( +
+
+ {filteredPeople.map(person => ( +
{ + setSelectedPerson(person); + setQuery(person.name); + setAppliedQuery(person.name); + onSelected?.(person); + }} + > +

{person.name}

+
+ ))}
-
+ )}
-
-

No matching suggestions

-
+ role="alert" + data-cy="no-suggestions-message" + > +

No matching suggestions

+
+ )}
); diff --git a/src/index.tsx b/src/index.tsx index 99587c20f..b1741f463 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,5 +2,8 @@ import { createRoot } from 'react-dom/client'; import 'bulma/css/bulma.css'; import { App } from './App'; +import { peopleFromServer } from './data/people'; -createRoot(document.getElementById('root') as HTMLDivElement).render(); +createRoot(document.getElementById('root') as HTMLDivElement).render( + , +); From 5c54856d44a5a30ccffa584730367da1b16ca856 Mon Sep 17 00:00:00 2001 From: anastasiiadns Date: Fri, 17 Apr 2026 13:39:33 +0300 Subject: [PATCH 2/3] fix of task --- src/App.tsx | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index b49cbf685..bb8ba1ad5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -17,10 +17,27 @@ export const App: React.FC = ({ persons, delay = 300, onSelected }) => { const applyQuery = useCallback(debounce(setAppliedQuery, delay), [delay]); + React.useEffect(() => { + return () => { + applyQuery.cancel?.(); + }; + }, [applyQuery]); + const handleQueryChange = (event: React.ChangeEvent) => { - setQuery(event.target.value); + const value = event.target.value; + + setQuery(value); setSelectedPerson(null); - applyQuery(event.target.value); + + const trimmed = value.trim(); + + if (!trimmed) { + setAppliedQuery(''); + + return; + } + + applyQuery(trimmed); }; const filteredPeople = persons.filter(person => @@ -77,7 +94,7 @@ export const App: React.FC = ({ persons, delay = 300, onSelected }) => { )} - {query && filteredPeople.length === 0 && ( + {(appliedQuery || query) && filteredPeople.length === 0 && (