From 10f05cb97564379b6de41bfaaa302d5fddc5c40b Mon Sep 17 00:00:00 2001 From: Yevhen Serdiuk Date: Fri, 17 Apr 2026 15:13:06 +0300 Subject: [PATCH 1/4] add solution --- README.md | 2 +- src/App.tsx | 71 ++++----------------- src/components/Autocomplete.tsx | 106 ++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 61 deletions(-) create mode 100644 src/components/Autocomplete.tsx diff --git a/README.md b/README.md index b8897503d..cb6ef193a 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://Yevhen-Srdk.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/src/App.tsx b/src/App.tsx index a88cd7a6d..1ad2088aa 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,73 +1,24 @@ -import React from 'react'; import './App.scss'; import { peopleFromServer } from './data/people'; +import { Person } from './types/Person'; +import { Autocomplete } from './components/Autocomplete'; +import { useState } from 'react'; export const App: React.FC = () => { - const { name, born, died } = peopleFromServer[0]; + const [selectedPerson, setSelectedPerson] = useState(null); return (

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

- -
-
- -
- -
-
-
-

Pieter Haverbeke

-
- -
-

Pieter Bernard Haverbeke

-
- -
-

Pieter Antone Haverbeke

-
- -
-

Elisabeth Haverbeke

-
- -
-

Pieter de Decker

-
- -
-

Petronella de Decker

-
- -
-

Elisabeth Hercke

-
-
-
-
- -
-

No matching suggestions

-
+
); diff --git a/src/components/Autocomplete.tsx b/src/components/Autocomplete.tsx new file mode 100644 index 000000000..036f34ee8 --- /dev/null +++ b/src/components/Autocomplete.tsx @@ -0,0 +1,106 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { Person } from '../types/Person'; +import classNames from 'classnames'; + +type Props = { + people: Person[]; + delay?: number; + onSelected: (person: Person | null) => void; +}; + +export const Autocomplete: React.FC = ({ + people, + delay = 300, + onSelected, +}) => { + const [query, setQuery] = useState(''); + const [isOpen, setIsOpen] = useState(false); + const [suggestions, setSuggestions] = useState(people); + const prevQuery = useRef(''); + + useEffect(() => { + const timeout = setTimeout(() => { + if (prevQuery.current === query) { + return; + } + + prevQuery.current = query; + + if (!query.trim()) { + setSuggestions(people); + + return; + } + + const filtered = people.filter(person => + person.name.toLowerCase().includes(query.toLowerCase()), + ); + + setSuggestions(filtered); + }, delay); + + return () => clearTimeout(timeout); + }, [query, delay, people]); + + const handleQueryChange = (event: React.ChangeEvent) => { + const value = event.target.value; + + setQuery(value); + onSelected(null); + }; + + const onFocus = () => { + setIsOpen(true); + }; + + return ( +
+
+ +
+ +
+
+ {suggestions.map((human: Person) => ( +
{ + setQuery(human.name); + setIsOpen(false); + onSelected(human); + }} + > +

{human.name}

+
+ ))} +
+
+ + {query.trim() && suggestions.length === 0 && ( +
+

No matching suggestions

+
+ )} +
+ ); +}; From 0d89c20ce29f1c3e4b05112f939bc87c8887beea Mon Sep 17 00:00:00 2001 From: Yevhen Serdiuk Date: Fri, 17 Apr 2026 15:25:11 +0300 Subject: [PATCH 2/4] renamed data-cy attribute to data-qa and renamed onFocus func to handleFocus --- src/components/Autocomplete.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/Autocomplete.tsx b/src/components/Autocomplete.tsx index 036f34ee8..493a59eab 100644 --- a/src/components/Autocomplete.tsx +++ b/src/components/Autocomplete.tsx @@ -49,7 +49,7 @@ export const Autocomplete: React.FC = ({ onSelected(null); }; - const onFocus = () => { + const handleFocus = () => { setIsOpen(true); }; @@ -60,20 +60,20 @@ export const Autocomplete: React.FC = ({ type="text" placeholder="Enter a part of the name" className="input" - data-cy="search-input" + data-qa="search-input" value={query} onChange={handleQueryChange} - onFocus={onFocus} + onFocus={handleFocus} /> -
+
{suggestions.map((human: Person) => (
{ setQuery(human.name); setIsOpen(false); @@ -96,7 +96,7 @@ export const Autocomplete: React.FC = ({ is-align-self-flex-start " role="alert" - data-cy="no-suggestions-message" + data-qa="no-suggestions-message" >

No matching suggestions

From 49a15c6327aa6836365614bd755f60d2641afb3f Mon Sep 17 00:00:00 2001 From: Yevhen Serdiuk Date: Fri, 17 Apr 2026 15:34:02 +0300 Subject: [PATCH 3/4] renamed data-cy attribute to data-qa in App.tsx --- src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App.tsx b/src/App.tsx index 1ad2088aa..6a818fa00 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,7 +10,7 @@ export const App: React.FC = () => { return (
-

+

{selectedPerson ? `${selectedPerson.name} (${selectedPerson.born} - ${selectedPerson.died})` : `No selected person`} From 4aa5fb1dc80d3284519e7f59abca3b896da4e671 Mon Sep 17 00:00:00 2001 From: Yevhen Serdiuk Date: Fri, 17 Apr 2026 15:43:11 +0300 Subject: [PATCH 4/4] renamed AGAIN data-qa attributes to data-cy in App.tsx & Autocomplete.tsx --- src/App.tsx | 2 +- src/components/Autocomplete.tsx | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 6a818fa00..1ad2088aa 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,7 +10,7 @@ export const App: React.FC = () => { return (
-

+

{selectedPerson ? `${selectedPerson.name} (${selectedPerson.born} - ${selectedPerson.died})` : `No selected person`} diff --git a/src/components/Autocomplete.tsx b/src/components/Autocomplete.tsx index 493a59eab..59f4200c1 100644 --- a/src/components/Autocomplete.tsx +++ b/src/components/Autocomplete.tsx @@ -60,20 +60,20 @@ export const Autocomplete: React.FC = ({ type="text" placeholder="Enter a part of the name" className="input" - data-qa="search-input" + data-cy="search-input" value={query} onChange={handleQueryChange} onFocus={handleFocus} />

-
+
{suggestions.map((human: Person) => (
{ setQuery(human.name); setIsOpen(false); @@ -96,7 +96,7 @@ export const Autocomplete: React.FC = ({ is-align-self-flex-start " role="alert" - data-qa="no-suggestions-message" + data-cy="no-suggestions-message" >

No matching suggestions