From 65feb0b2a78dd0c182ccc28990057d96775ac977 Mon Sep 17 00:00:00 2001 From: Banderos14 Date: Thu, 7 May 2026 15:39:02 +0200 Subject: [PATCH 1/2] feat: task_solution --- README.md | 2 +- src/App.tsx | 109 ++++++++++++++++++++++++++++++---------------------- 2 files changed, 65 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index b8897503d..def09a5f5 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://Banderos14.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..1c578cf43 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,73 +1,92 @@ -import React from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import './App.scss'; import { peopleFromServer } from './data/people'; +import { Person } from './types/Person'; export const App: React.FC = () => { - const { name, born, died } = peopleFromServer[0]; + const [query, setQuery] = useState(''); + const [appliedQuery, setAppliedQuery] = useState(''); + const [selectedPerson, setSelectedPerson] = useState(null); + const [isFocused, setIsFocused] = useState(false); + + useEffect(() => { + const timerId = window.setTimeout(() => { + setAppliedQuery(query.trim()); + }, 300); + + return () => window.clearTimeout(timerId); + }, [query]); + + const visiblePeople = useMemo(() => { + if (!appliedQuery) { + return peopleFromServer; + } + + return peopleFromServer.filter(person => + person.name.toLowerCase().includes(appliedQuery.toLowerCase()), + ); + }, [appliedQuery]); + + const handleChange = (event: React.ChangeEvent) => { + const value = event.target.value; + + setQuery(value); + + if (selectedPerson && value !== selectedPerson.name) { + setSelectedPerson(null); + } + }; + + const handleSelect = (person: Person) => { + setSelectedPerson(person); + setQuery(person.name); + setAppliedQuery(person.name); + setIsFocused(false); + }; return (

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

-
+
setIsFocused(true)} />
-
-

Pieter Haverbeke

-
- -
-

Pieter Bernard Haverbeke

-
- -
-

Pieter Antone Haverbeke

-
- -
-

Elisabeth Haverbeke

-
- -
-

Pieter de Decker

-
- -
-

Petronella de Decker

-
- -
-

Elisabeth Hercke

-
+ {visiblePeople.length > 0 ? ( + visiblePeople.map(person => ( +
handleSelect(person)} + > +

{person.name}

+
+ )) + ) : ( +
+ No matching suggestions +
+ )}
- -
-

No matching suggestions

-
); From 1b532f1ef7e4a927e0156c646c0a0ff4e103be41 Mon Sep 17 00:00:00 2001 From: Banderos14 Date: Thu, 7 May 2026 15:46:48 +0200 Subject: [PATCH 2/2] fix: added file autocomplete and logic has been transfered from app.tsx --- src/App.tsx | 81 +++------------------------ src/components/Autocomplete.tsx | 97 +++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 73 deletions(-) create mode 100644 src/components/Autocomplete.tsx diff --git a/src/App.tsx b/src/App.tsx index 1c578cf43..e1dbb1028 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,48 +1,12 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useState } from 'react'; import './App.scss'; + import { peopleFromServer } from './data/people'; import { Person } from './types/Person'; +import { Autocomplete } from './components/Autocomplete'; export const App: React.FC = () => { - const [query, setQuery] = useState(''); - const [appliedQuery, setAppliedQuery] = useState(''); const [selectedPerson, setSelectedPerson] = useState(null); - const [isFocused, setIsFocused] = useState(false); - - useEffect(() => { - const timerId = window.setTimeout(() => { - setAppliedQuery(query.trim()); - }, 300); - - return () => window.clearTimeout(timerId); - }, [query]); - - const visiblePeople = useMemo(() => { - if (!appliedQuery) { - return peopleFromServer; - } - - return peopleFromServer.filter(person => - person.name.toLowerCase().includes(appliedQuery.toLowerCase()), - ); - }, [appliedQuery]); - - const handleChange = (event: React.ChangeEvent) => { - const value = event.target.value; - - setQuery(value); - - if (selectedPerson && value !== selectedPerson.name) { - setSelectedPerson(null); - } - }; - - const handleSelect = (person: Person) => { - setSelectedPerson(person); - setQuery(person.name); - setAppliedQuery(person.name); - setIsFocused(false); - }; return (
@@ -53,40 +17,11 @@ export const App: React.FC = () => { : 'No selected person'} -
-
- setIsFocused(true)} - /> -
- -
-
- {visiblePeople.length > 0 ? ( - visiblePeople.map(person => ( -
handleSelect(person)} - > -

{person.name}

-
- )) - ) : ( -
- No matching suggestions -
- )} -
-
-
+
); diff --git a/src/components/Autocomplete.tsx b/src/components/Autocomplete.tsx new file mode 100644 index 000000000..d6b1a5739 --- /dev/null +++ b/src/components/Autocomplete.tsx @@ -0,0 +1,97 @@ +import { useEffect, useMemo, useState } from 'react'; + +import { Person } from '../types/Person'; + +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 [appliedQuery, setAppliedQuery] = useState(''); + const [isFocused, setIsFocused] = useState(false); + const [selectedPerson, setSelectedPerson] = useState(null); + + useEffect(() => { + const timerId = window.setTimeout(() => { + setAppliedQuery(query.trim()); + }, delay); + + return () => window.clearTimeout(timerId); + }, [query, delay]); + + const visiblePeople = useMemo(() => { + if (!appliedQuery) { + return people; + } + + return people.filter(person => + person.name.toLowerCase().includes(appliedQuery.toLowerCase()), + ); + }, [people, appliedQuery]); + + const handleChange = (event: React.ChangeEvent) => { + const value = event.target.value; + + setQuery(value); + + if (selectedPerson && value !== selectedPerson.name) { + setSelectedPerson(null); + onSelected(null); + } + }; + + const handleSelect = (person: Person) => { + setSelectedPerson(person); + + setQuery(person.name); + setAppliedQuery(person.name); + + setIsFocused(false); + + onSelected(person); + }; + + return ( +
+
+ setIsFocused(true)} + /> +
+ +
+
+ {visiblePeople.length > 0 ? ( + visiblePeople.map(person => ( +
handleSelect(person)} + > +

{person.name}

+
+ )) + ) : ( +
+ No matching suggestions +
+ )} +
+
+
+ ); +};