Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<your_account>` with your Github username in the [DEMO LINK](https://<your_account>.github.io/react_autocomplete/) and add it to the PR description.
- Replace `<your_account>` 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
Expand Down
70 changes: 11 additions & 59 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -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<Person | null>(null);

return (
<div className="container">
<main className="section is-flex is-flex-direction-column">
<h1 className="title" data-cy="title">
{`${name} (${born} - ${died})`}
{selectedPerson
? `${selectedPerson.name} (${selectedPerson.born} - ${selectedPerson.died})`
: 'No selected person'}
</h1>

<div className="dropdown is-active">
<div className="dropdown-trigger">
<input
type="text"
placeholder="Enter a part of the name"
className="input"
data-cy="search-input"
/>
</div>

<div className="dropdown-menu" role="menu" data-cy="suggestions-list">
<div className="dropdown-content">
<div className="dropdown-item" data-cy="suggestion-item">
<p className="has-text-link">Pieter Haverbeke</p>
</div>

<div className="dropdown-item" data-cy="suggestion-item">
<p className="has-text-link">Pieter Bernard Haverbeke</p>
</div>

<div className="dropdown-item" data-cy="suggestion-item">
<p className="has-text-link">Pieter Antone Haverbeke</p>
</div>

<div className="dropdown-item" data-cy="suggestion-item">
<p className="has-text-danger">Elisabeth Haverbeke</p>
</div>

<div className="dropdown-item" data-cy="suggestion-item">
<p className="has-text-link">Pieter de Decker</p>
</div>

<div className="dropdown-item" data-cy="suggestion-item">
<p className="has-text-danger">Petronella de Decker</p>
</div>

<div className="dropdown-item" data-cy="suggestion-item">
<p className="has-text-danger">Elisabeth Hercke</p>
</div>
</div>
</div>
</div>

<div
className="
notification
is-danger
is-light
mt-3
is-align-self-flex-start
"
role="alert"
data-cy="no-suggestions-message"
>
<p className="has-text-danger">No matching suggestions</p>
</div>
<Autocomplete
people={peopleFromServer}
onSelected={setSelectedPerson}
/>
</main>
</div>
);
Expand Down
100 changes: 100 additions & 0 deletions src/Autocomplete.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<div className={isOpen ? 'dropdown is-active' : 'dropdown'}>
<div className="dropdown-trigger">
<input
type="text"
placeholder="Enter a part of the name"
className="input"
data-cy="search-input"
value={query}
onChange={event => {
const value = event.target.value;

if (value !== query) {
onSelected(null);
}

setQuery(value);
applyQuery(value);
Comment on lines +50 to +67
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checklist item #1 violation: The filter will run even if user enters spaces only. Add a check like value.trim() !== '' before calling applyQuery(value) to prevent filtering on whitespace-only input.

}}
onFocus={() => setIsOpen(true)}
/>
</div>

<div className="dropdown-menu" role="menu" data-cy="suggestions-list">
<div className="dropdown-content">
{visiblePeople.map(person => (
<div
className="dropdown-item"
data-cy="suggestion-item"
key={person.slug}
onClick={() => {
setQuery(person.name);
setAppliedQuery(person.name);
setIsOpen(false);
onSelected(person);
}}
>
<p
className={
person.sex === 'm' ? 'has-text-link' : 'has-text-danger'
}
>
{person.name}
</p>
</div>
))}
</div>
</div>
</div>

{isOpen && visiblePeople.length === 0 && (
<div
className="
notification
is-danger
is-light
mt-3
is-align-self-flex-start
"
role="alert"
data-cy="no-suggestions-message"
>
<p className="has-text-danger">No matching suggestions</p>
</div>
)}
</>
);
};
21 changes: 14 additions & 7 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -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"]
}
Comment on lines +15 to 17
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checklist item #1 violation: The filter runs for spaces-only input. Add a check in the visiblePeople filtering logic to handle empty or whitespace-only appliedQuery values. When appliedQuery is empty or contains only spaces, visiblePeople should likely be empty as well, or the filtering should be skipped entirely.

Loading