Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
71 changes: 11 additions & 60 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,73 +1,24 @@
import React from 'react';
import './App.scss';
import React, { useState } from 'react';
import { Autocomplete } from './components/Autocomplete';
import { Person } from './types/Person';
import { peopleFromServer } from './data/people';

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>
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: "don't generate key on render" — in peopleList.tsx the list uses key={person.slug}, which is stable and correct. No action needed here. (No violating pattern found.)


<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
85 changes: 85 additions & 0 deletions src/components/Autocomplete.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React, { useEffect, useState } from 'react';
import { Person } from '../types/Person';
import { useFilter } from '../hooks/Filter';
import { PeopleList } from './peopleList';

interface Props {
people: Person[];
delay?: number;
onSelected: (person: Person | null) => void;
}

export const Autocomplete: React.FC<Props> = ({
people,
delay = 300,
onSelected,
}) => {
const [rawInput, setRawInput] = useState('');
const [query, setQuery] = useState('');
const [show, setShow] = useState(false);

const filteredPeople = useFilter(people, query);

// 🔹 debounce
useEffect(() => {
const trimmed = rawInput.trim();

const timer = setTimeout(() => {
setQuery(trimmed); // даже пустая строка → покажет всех
Comment on lines +24 to +28
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This violates checklist item #1: "make sure that filter won't be called if user enred spaces only;"

In Autocomplete.tsx the debounce effect sets const trimmed = rawInput.trim(); then always calls setQuery(trimmed) after delay, which means if user types only spaces (rawInput consists of spaces) trimmed becomes empty string and setQuery('') will cause the filter hook to run and return all people. The requirement asks that filter shouldn't be called if user entered spaces only — you should avoid updating query (or avoid triggering filter) when the input is spaces-only. Consider skipping setQuery when rawInput contains only whitespace (e.g. if rawInput.length > 0 && trimmed === '').

}, delay);

return () => clearTimeout(timer);
Comment on lines +24 to +31
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This violates the checklist instruction: "don't run filtering again if the text has not changed (a pause in typing happened when the text was the same as before)". The current debounce effect depends only on [rawInput, delay] and always sets query to trimmed value after each timer. If rawInput (after trimming) equals the previous query, you still call setQuery which is unnecessary and may re-run filtering. Add a guard to only call setQuery when trimmed !== query (or track previous trimmed value) to avoid redundant filtering runs.

}, [rawInput, delay]);

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;

setRawInput(value);
setShow(true);

// 🔴 ВАЖНО: сбрасываем выбранного человека
onSelected(null);
Comment on lines +34 to +41
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Small issue related to Checklist: Although you reset selected person via onSelected(null) in handleChange, there's no differentiation between raw input changes that are only whitespace. Per requirement, when the selected person is shown, and the input value changes, the selected person should be cleared. But if input change is spaces-only and you decide to ignore spaces for filtering, you still must clear the selected person. Ensure you still call onSelected(null) on every input mutation (including spaces) if the intent is to clear selection; or document the intended behavior and adjust accordingly.

};

const handleSelect = (person: Person) => {
setRawInput(person.name);
setQuery(person.name);
setShow(false);
onSelected(person);
};

const showError = show && query !== '' && filteredPeople.length === 0;

return (
<div className={`dropdown ${show ? 'is-active' : ''}`}>
<div className="dropdown-trigger">
<input
type="text"
className="input"
data-cy="search-input"
placeholder="Enter a part of the name"
value={rawInput}
onChange={handleChange}
onFocus={() => setShow(true)}
/>
</div>

{show && (
<div className="dropdown-menu" data-cy="suggestions-list">
<div className="dropdown-content">
<PeopleList people={filteredPeople} onSelect={handleSelect} />
</div>
</div>
)}

{showError && (
<div
className="notification is-danger is-light mt-2"
data-cy="no-suggestions-message"
>
No matching suggestions
</div>
)}
</div>
);
};
26 changes: 26 additions & 0 deletions src/components/peopleList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { Person } from '../types/Person';

interface PeopleListProps {
Comment on lines +1 to +3
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: "Don't interact with DOM directly, use React as much as possible" — code uses React state and props, no direct DOM manipulation found. Satisfied.

people: Person[];
onSelect?: (person: Person) => void;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This file doesn't prevent filtering when the input contains only spaces. The checklist requires: "make sure that filter won't be called if user enred spaces only". In useFilter, the condition if (!query.trim()) { return people; } will treat a spaces-only query as empty and return all people (which avoids running .filter), but higher-level requirement is to not call filter when user entered spaces only. The current useFilter implementation avoids filtering, so it's OK here. However, in App.tsx handleInputChange sets setQuery(event.target.value) directly for spaces, which will cause useFilter to run but will short-circuit; this is acceptable. No change required for this specific rule.


export const PeopleList = ({
people,
onSelect = () => {},
}: PeopleListProps) => {
return (
<>
{people.map(person => (
Comment on lines +1 to +14
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The task's debounce requirement and customizable delay prop for Autocomplete are not implemented in these files. There is no Autocomplete component here — App uses useFilter directly and shows suggestions immediately controlled by show and query. The description requires an Autocomplete component with debounce behavior and a delay prop (default 300ms). So this implementation is missing that component and debounce logic.

<div
key={person.slug}
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: "Don't generate key on render" — PeopleList uses key={person.slug}, which is a stable key (not generated each render). Satisfied.

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: "don't generate key on render" is satisfied (you use stable person.slug), so no issue there. However, ensure slug is unique for all people — otherwise using index or generated keys on render would violate the checklist. (See peopleList key usage at line 16)

className="dropdown-item has-text-link is-clickable"
data-cy="suggestion-item"
onClick={() => onSelect(person)}
>
{person.name}
</div>
))}
Comment on lines +22 to +23
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Requirement: "Show No matching suggestions message if there are no people containing the entered text" — App computes error in useEffect and renders the message when show && query.trim() !== '' && filteredPeople.length === 0. That meets the requirement. No change needed.

</>
);
};
14 changes: 14 additions & 0 deletions src/hooks/Filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useMemo } from 'react';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This violates checklist item #2: 'don't interact with DOM directly, use React as much as possible'. The implementation doesn't interact with the DOM directly, so no issue here (no direct DOM access found).

import { Person } from '../types/Person';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing requirement: 'Implement an Autocomplete component that will suggest people matching an entered text.' There is no separate Autocomplete component in the submitted files; App implements the dropdown directly and uses PeopleList and useFilter. You must implement a dedicated Autocomplete component with props (people, delay, onSelected, etc.) per the spec.


Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Requirement: 'The delay should be customizable via props (default value is 300ms)'. No component accepts a delay prop or provides default. Add delay prop and default value.

export const useFilter = (people: Person[], query: string) => {
return useMemo(() => {
if (!query.trim()) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This violates checklist item #1: 'make sure that filter won't be called if user enred spaces only'. The current filter hook calls query.trim() and returns all people when trimming yields empty — that prevents filtering on spaces-only input in useFilter. However, the App's handleInputChange sets setQuery(event.target.value) directly and clears selectedPerson; so spaces-only input becomes a non-empty query in state until useFilter's trim check treats it as empty. This behavior meets the requirement (filter won't be run for spaces-only). No change needed.

return people; // 👈 show all when empty
}

return people.filter(person =>
person.name.toLowerCase().includes(query.toLowerCase()),
);
}, [people, query]);
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing debounce requirement: The description requires suggestions appear after some delay in typing (debounce) with a customizable delay prop (default 300ms). Neither App nor useFilter implements debouncing or accepts a delay prop. Implement debounce logic (e.g., in a custom hook or in App) that delays updating the query passed to the filter, and expose a prop to configure the delay with default 300ms. Without this, the core functional requirement is not satisfied.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing requirement: 'Don't run filtering again if the text has not changed (a pause in typing happened when the text was the same as before)'. There's no logic preventing repeated filtering on identical query pauses — currently useFilter depends on query and people, so if query doesn't change, filter won't rerun, but because App sets query from the raw input on every onChange, repeated identical values won't happen. However the debounce behavior that's missing could cause redundant filtering; implement debounce and ensure you compare previous filtered text to avoid re-filtering when the trimmed query equals previous trimmed query.

Loading