From b9497c510c9ebf6b113596defb58ae80986ff9a3 Mon Sep 17 00:00:00 2001 From: Roman Zhyla Date: Tue, 5 May 2026 12:44:37 +0200 Subject: [PATCH 1/2] add solution --- README.md | 2 +- cypress.config.ts | 2 +- src/App.tsx | 110 +++++++++++++++++++++++++++------------------- 3 files changed, 67 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index b8897503d..8f4080bc0 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://ZhylaRoman.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/cypress.config.ts b/cypress.config.ts index 6aa317d01..c0d0d1081 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -2,7 +2,7 @@ const { defineConfig } = require('cypress'); module.exports = defineConfig({ e2e: { - baseUrl: 'http://localhost:3000', + baseUrl: 'http://localhost:3005', specPattern: 'cypress/integration/**/*.spec.{js,ts,jsx,tsx}', }, video: true, diff --git a/src/App.tsx b/src/App.tsx index a88cd7a6d..61a916646 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,73 +1,93 @@ -import React from 'react'; +import React, { 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]; +interface AppProps { + onSelected?: (person: Person) => void; +} + +export const App: React.FC = ({ onSelected }) => { + const [query, setQuery] = useState(''); + const [isOpen, setIsOpen] = useState(false); + const [selectedPerson, setSelectedPerson] = useState(null); + + const normalizedQuery = query.trim().toLowerCase(); + + const filteredPeople = useMemo(() => { + if (!normalizedQuery) { + return peopleFromServer; + } + + return peopleFromServer.filter(person => + person.name.toLowerCase().includes(normalizedQuery), + ); + }, [normalizedQuery]); + + const showNoResults = normalizedQuery !== '' && filteredPeople.length === 0; + + const handleSelect = (person: Person) => { + setSelectedPerson(person); + setQuery(person.name); + setIsOpen(false); + onSelected?.(person); + }; return (

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

-
+
setIsOpen(true)} + onChange={event => { + setQuery(event.target.value); + setSelectedPerson(null); + }} + onBlur={() => setIsOpen(false)} />
-
-

Pieter Haverbeke

-
- -
-

Pieter Bernard Haverbeke

-
- -
-

Pieter Antone Haverbeke

-
- -
-

Elisabeth Haverbeke

-
- -
-

Pieter de Decker

-
- -
-

Petronella de Decker

-
- -
-

Elisabeth Hercke

-
+ {filteredPeople.map(person => ( +
handleSelect(person)} + > +

{person.name}

+
+ ))}
-
-

No matching suggestions

-
+ {showNoResults && ( +
+

No matching suggestions

+
+ )}
); From bb161d73811e59dfd3cfb9e6186f6497cb93d564 Mon Sep 17 00:00:00 2001 From: Roman Zhyla Date: Tue, 5 May 2026 13:00:02 +0200 Subject: [PATCH 2/2] solution --- src/App.tsx | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 61a916646..b63b2d263 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,18 +1,21 @@ -import React, { useMemo, useState } from 'react'; +import React, { useMemo, useRef, useState } from 'react'; import './App.scss'; import { peopleFromServer } from './data/people'; import { Person } from './types/Person'; interface AppProps { onSelected?: (person: Person) => void; + delay?: number; } -export const App: React.FC = ({ onSelected }) => { +export const App: React.FC = ({ onSelected, delay = 300 }) => { const [query, setQuery] = useState(''); + const [appliedQuery, setAppliedQuery] = useState(''); const [isOpen, setIsOpen] = useState(false); const [selectedPerson, setSelectedPerson] = useState(null); + const timerId = useRef | null>(null); - const normalizedQuery = query.trim().toLowerCase(); + const normalizedQuery = appliedQuery.trim().toLowerCase(); const filteredPeople = useMemo(() => { if (!normalizedQuery) { @@ -29,10 +32,25 @@ export const App: React.FC = ({ onSelected }) => { const handleSelect = (person: Person) => { setSelectedPerson(person); setQuery(person.name); + setAppliedQuery(person.name); setIsOpen(false); onSelected?.(person); }; + const handleChange = (event: React.ChangeEvent) => { + setQuery(event.target.value); + setSelectedPerson(null); + setIsOpen(true); + + if (timerId.current) { + clearTimeout(timerId.current); + } + + timerId.current = setTimeout(() => { + setAppliedQuery(event.target.value); + }, delay); + }; + return (
@@ -54,10 +72,7 @@ export const App: React.FC = ({ onSelected }) => { data-cy="search-input" value={query} onFocus={() => setIsOpen(true)} - onChange={event => { - setQuery(event.target.value); - setSelectedPerson(null); - }} + onChange={handleChange} onBlur={() => setIsOpen(false)} />
@@ -69,7 +84,10 @@ export const App: React.FC = ({ onSelected }) => { key={person.slug} className="dropdown-item" data-cy="suggestion-item" - onMouseDown={() => handleSelect(person)} + onMouseDown={event => { + event.preventDefault(); + handleSelect(person); + }} >

{person.name}