From ee420ccdc48b16b7d6570f5a0392f1ae0267d3d7 Mon Sep 17 00:00:00 2001 From: Anastasiia Holubko Date: Mon, 27 Apr 2026 00:42:05 +0200 Subject: [PATCH 1/2] Solution --- README.md | 2 +- src/App.tsx | 70 +++++------------------------- src/Autocomplete.tsx | 100 +++++++++++++++++++++++++++++++++++++++++++ tsconfig.json | 21 ++++++--- 4 files changed, 126 insertions(+), 67 deletions(-) create mode 100644 src/Autocomplete.tsx diff --git a/README.md b/README.md index b8897503d..c95aa36b1 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://aholubko.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..8b5ca600e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,73 +1,25 @@ -import React from 'react'; +import React, { useState } from 'react'; import './App.scss'; import { peopleFromServer } from './data/people'; +import { Person } from './types/Person'; +import { Autocomplete } from './Autocomplete'; 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/Autocomplete.tsx b/src/Autocomplete.tsx new file mode 100644 index 000000000..3aa6e38f0 --- /dev/null +++ b/src/Autocomplete.tsx @@ -0,0 +1,100 @@ +import { useEffect, useMemo, useState } from 'react'; +import { Person } from './types/Person'; +import debounce from 'lodash.debounce'; + +type Props = { + people: Person[]; + onSelected: (person: Person | null) => void; + delay?: number; +}; + +export const Autocomplete = ({ people, onSelected, delay = 300 }: Props) => { + const [query, setQuery] = useState(''); + const [isOpen, setIsOpen] = useState(false); + const [appliedQuery, setAppliedQuery] = useState(''); + + const visiblePeople = people.filter(person => + person.name.toLowerCase().includes(appliedQuery.toLowerCase()), + ); + + const applyQuery = useMemo(() => { + return debounce((value: string) => { + setAppliedQuery(value); + }, delay); + }, [delay]); + + useEffect(() => { + return () => { + applyQuery.cancel(); + }; + }, [applyQuery]); + + return ( + <> +
+
+ { + const value = event.target.value; + + if (value !== query) { + onSelected(null); + } + + setQuery(value); + applyQuery(value); + }} + onFocus={() => setIsOpen(true)} + /> +
+ +
+
+ {visiblePeople.map(person => ( +
{ + setQuery(person.name); + setAppliedQuery(person.name); + setIsOpen(false); + onSelected(person); + }} + > +

+ {person.name} +

+
+ ))} +
+
+
+ + {isOpen && visiblePeople.length === 0 && ( +
+

No matching suggestions

+
+ )} + + ); +}; diff --git a/tsconfig.json b/tsconfig.json index cfb168bb2..fe6cc775a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,17 @@ { - "extends": "@mate-academy/students-ts-config", - "include": [ - "src" - ], "compilerOptions": { - "sourceMap": false, - "types": ["node", "cypress"] - } + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "Bundler", + "allowImportingTsExtensions": false, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true + }, + "include": ["src"] } From 560c584cc4349a99c1b0ba7f9649405a33b4f871 Mon Sep 17 00:00:00 2001 From: Anastasiia Holubko Date: Mon, 27 Apr 2026 00:55:10 +0200 Subject: [PATCH 2/2] Fixed mistakes --- src/Autocomplete.tsx | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/Autocomplete.tsx b/src/Autocomplete.tsx index 3aa6e38f0..1e0ef4834 100644 --- a/src/Autocomplete.tsx +++ b/src/Autocomplete.tsx @@ -13,9 +13,17 @@ export const Autocomplete = ({ people, onSelected, delay = 300 }: Props) => { const [isOpen, setIsOpen] = useState(false); const [appliedQuery, setAppliedQuery] = useState(''); - const visiblePeople = people.filter(person => - person.name.toLowerCase().includes(appliedQuery.toLowerCase()), - ); + const normalizedQuery = appliedQuery.trim().toLowerCase(); + + let visiblePeople: Person[]; + + if (normalizedQuery === '') { + visiblePeople = people; + } else { + visiblePeople = people.filter(person => + person.name.toLowerCase().includes(normalizedQuery), + ); + } const applyQuery = useMemo(() => { return debounce((value: string) => { @@ -47,6 +55,15 @@ export const Autocomplete = ({ people, onSelected, delay = 300 }: Props) => { } setQuery(value); + onSelected(null); + + if (value.trim() === '') { + setAppliedQuery(''); + applyQuery.cancel(); + + return; + } + applyQuery(value); }} onFocus={() => setIsOpen(true)}