
Компонент автозаполнения поиска с React и TypeScript
28 ноября 2022 г.Как показать предложения данных из API Rest
В настоящее время одним из наиболее широко используемых компонентов веб-сайта являются поисковые системы с автозаполнением или подсказками.
Обычно это первый компонент, с которым взаимодействует пользователь, так как практичнее выполнить поиск и сразу перейти к тому, что нам нужно. Эти компоненты необходимы для удобства пользователей на таких сайтах, как интернет-магазины.
В этом руководстве мы создадим простой компонент поиска, который предлагает пользователям подсказки о том, что они печатают, без сторонних библиотек. п
Что такое автозаполнение поиска?
Автозаполнение – это шаблон, используемый для отображения предложений запроса.
Поиск с автозаполнением, также называемый "интеллектуальным поиском" или "автопредложением", представляет собой компонент, который пользователь вводит в поле ввода и предлагает различные прогнозы или возможные результаты того, как поиск может быть завершен.
Автозаполнение работает с поисковой системой, которая учится и улучшает предлагаемые результаты по мере того, как ее пользователи выполняют поиск.
В этом случае мы не будем больше говорить о поисковых системах, потому что это выходит за рамки учебника. Если вы хотите узнать больше об этой теме, посетите этот сайт. . Без лишних слов перейдем к программированию.
Настройка автозаполнения поиска
Мы создаем наше приложение с приглашением со следующими команда:
yarn create vite autocomplete-search --template react-ts
Устанавливаем зависимости, которые нам понадобятся в проекте:
yarn add @nextui-org/react
В этом случае я буду использовать стороннюю библиотеку только для стилей, которые вы можете использовать как хотите:
* nextui фреймворк Javascript/CSS
После этого мы создаем следующую структуру папок для проекта:
src/
├── components/
│ └── ui/
│ ├── Autocomplete.tsx
│ ├── Autocomplete.tsx
│ ├── index.ts
│ └── ui.module.css
├── hooks/
│ └── useAutocomplete.ts
├── ts/
│ └──interfaces/
│ └── Country.interface.ts
├── App.tsx
└── main.tsx
Компоненты
AutocompleteWrapper.tsx
Этот компонент используется только как контейнер или оболочка для Autocomplete.tsx
, и именно здесь мы будем запрашивать нужные нам данные.
Мы используем API restcountries для руководства и используем только англоязычные страны, поэтому запрос будет выглядеть следующим образом:
https://restcountries.com/v3.1/lang/eng
Autocomplete.tsx
Это основной компонент, состоящий из двух разделов. Первый раздел — это элемент ввода, а второй — список предложений.
Обычно используется элемент <ul>
или <ol>
, но в этом руководстве мы будем использовать компоненты Rows внутри компонента Next UI Card.
import { Card, Col, Input, Row, Text, User } from "@nextui-org/react"
import { useEffect, useRef, useState } from "react"
import { Country } from "../../ts/interfaces/Country.interface"
import classes from "./ui.module.css"
interface Props {
data: Country[];
}
const Autocomplete = ({ data }: Props) => {
const inputSearchRef = useRef < HTMLInputElement > null
const [searchedValue, setSearchedValue] = useState("")
const [suggestions, setSuggestions] = useState < Country[]> []
const [selectedSuggestion, setSelectedSuggestion] = useState("")
const [activeSuggestion, setActiveSuggestion] = useState(0)
useEffect(() => {
if (inputSearchRef.current) {
inputSearchRef.current.focus()
}
}, [])
const handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
if (event.target.value !== "") {
const filteredSuggestions = data.filter((itemData) => {
const value = event.target.value.toUpperCase()
const name = itemData.name.common.toUpperCase()
return value && name.startsWith(value) && name !== value
})
setSearchedValue(event.target.value)
setSuggestions(filteredSuggestions)
}
}
const handleKeyDown = (
event: React.KeyboardEvent<HTMLInputElement>
): void => {
if (event.key === "ArrowDown" && activeSuggestion < suggestions.length) {
setActiveSuggestion(activeSuggestion + 1)
} else if (event.key === "ArrowUp" && activeSuggestion > 1) {
setActiveSuggestion(activeSuggestion - 1)
} else if (event.key === "Enter") {
setSearchedValue(suggestions[activeSuggestion - 1].name.common)
setSelectedSuggestion(suggestions[activeSuggestion - 1].name.common)
setSuggestions([])
setActiveSuggestion(0)
}
}
const handleClick = (value: string) => {
setSelectedSuggestion(value)
setSearchedValue(value)
setSuggestions([])
setActiveSuggestion(0)
//do something else
}
return (
<div className={classes.autocomplete}>
<Input
bordered
labelPlaceholder="Search your Country"
size="xl"
value={searchedValue}
onChange={handleChange}
onKeyDown={handleKeyDown}
ref={inputSearchRef}
color="secondary"
/>
<Card css={{ marginTop: "0.5rem" }}>
<Card.Body css={{ padding: "0" }}>
{!suggestions.length &&
searchedValue.length &&
!selectedSuggestion.length ? (
<Row className={classes.itemListNot}>
<Col>
<Text>Nothing to show :(</Text>
</Col>
</Row>
) : (
<>
{suggestions.map(({ name, flags }: Country, index) => (
<Row
key={index}
className={`${classes.itemList} ${
index === activeSuggestion - 1 ? classes.activeItem : ""
}`}
onClick={() => handleClick(name.common)}
>
<Col>
<User src={flags.svg} name={name.common} squared />
</Col>
</Row>
))}
</>
)}
</Card.Body>
</Card>
<Text size="$xs">Country selected: {selectedSuggestion}</Text>
</div>
)
}
export default Autocomplete
Сначала создадим нужные нам типы. API возвращает нам большое количество данных, которые мы не будем использовать, поэтому упрощение информации и типов будет выглядеть так:
export type Country = {
name: Name
flags: Flags
}
type Name = {
common: string
}
type Flags = {
png: string
svg: string
}
После этого создадим следующие состояния:
searchedValue
— Здесь мы будем хранить текст, который набирает пользователь.
suggestions
— здесь мы будем хранить предложения, которые соответствуют тому, что пишет пользователь.
selectedSuggestion
— Здесь мы будем хранить параметр, выбранный пользователем.
activeSuggestion
— Здесь мы будем хранить индекс показанных предложений. Мы будем использовать его, чтобы узнать, какое предложение выбрано клавиатурой.
Теперь нам нужно создать функции, которые будут реагировать на события элемента ввода и списка результатов.
handleChange()
Эта функция будет выполняться каждый раз, когда пользователь что-то вводит в элемент ввода. Мы проверим, не является ли введенное значение пустым. В противном случае мы установим состояния в их начальные значения.
Если значение, полученное в элементе ввода, не является пустым, функция будет выполнена и отобразит предложения, соответствующие введенному значению.
handleClick()
Эта функция будет выполняться, когда пользователь выбирает предложение; мы сохраняем выбранное значение и устанавливаем остальные состояния в их начальные значения.
handleKeyDown()
Эта функция будет выполняться при обнаружении события на клавиатуре, поэтому вы можете просмотреть предложения и выбрать одно из них.
Наконец, мы добавили useEffect
, чтобы сосредоточиться на элементе ввода при монтировании компонента.
Это все! У нас уже есть поиск с автозаполнением, который мы можем использовать в любом проекте, передав входную ссылку и данные в фильтр.
В качестве дополнительного шага и хорошей практики мы перенесем функциональность в пользовательский хук, и наш компонент станет чище и читабельнее.
import { useEffect, useState } from "react";
import { Country } from "../ts/interfaces/Country.interface";
const useAutocomplete = (
data: Country[],
inputSearchRef: HTMLInputElement | null
) => {
const [searchedValue, setSearchedValue] = useState("");
const [suggestions, setSuggestions] = useState < Country[] > [];
const [selectedSuggestion, setSelectedSuggestion] = useState("");
const [activeSuggestion, setActiveSuggestion] = useState(0);
useEffect(() => {
if (inputSearchRef) {
inputSearchRef.focus();
}
}, []);
const handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
if (event.target.value !== "") {
const filteredSuggestions = data.filter((itemData) => {
const value = event.target.value.toUpperCase();
const name = itemData.name.common.toUpperCase();
return value && name.startsWith(value) && name !== value;
});
setSearchedValue(event.target.value);
setSuggestions(filteredSuggestions);
} else {
setSearchedValue("");
setSuggestions([]);
setSelectedSuggestion("");
setActiveSuggestion(0);
}
};
const handleKeyDown = (
event: React.KeyboardEvent<HTMLInputElement>
): void => {
if (event.key === "ArrowDown" && activeSuggestion < suggestions.length) {
setActiveSuggestion(activeSuggestion + 1);
} else if (event.key === "ArrowUp" && activeSuggestion > 1) {
setActiveSuggestion(activeSuggestion - 1);
} else if (event.key === "Enter") {
setSearchedValue(suggestions[activeSuggestion - 1].name.common);
setSelectedSuggestion(suggestions[activeSuggestion - 1].name.common);
setSuggestions([]);
setActiveSuggestion(0);
}
};
const handleClick = (value: string) => {
setSearchedValue(value);
setSuggestions([]);
setSelectedSuggestion(value);
setActiveSuggestion(0);
//do something else
};
return {
searchedValue,
suggestions,
selectedSuggestion,
activeSuggestion,
handleChange,
handleKeyDown,
handleClick,
};
};
export default useAutocomplete;
import { useEffect, useRef } from "react";
import { Card, Col, Input, Row, Text, User } from "@nextui-org/react";
import { Country } from "../../ts/interfaces/Country.interface";
import useAutocomplete from "../../hooks/useAutocomplete";
import classes from "./ui.module.css";
interface Props {
data: Country[];
}
const Autocomplete = ({ data }: Props) => {
const inputSearchRef = useRef < HTMLInputElement > null;
useEffect(() => {
if (inputSearchRef.current) {
inputSearchRef.current.focus();
}
}, []);
const {
searchedValue,
suggestions,
selectedSuggestion,
activeSuggestion,
handleChange,
handleKeyDown,
handleClick,
} = useAutocomplete(data, inputSearchRef.current);
return (
<div className={classes.autocomplete}>
<Input
bordered
labelPlaceholder="Search your Country"
size="xl"
value={searchedValue}
onChange={handleChange}
onKeyDown={handleKeyDown}
ref={inputSearchRef}
color="secondary"
/>
<Card css={{ marginTop: "0.5rem" }}>
<Card.Body css={{ padding: "0" }}>
{!suggestions.length &&
searchedValue.length &&
!selectedSuggestion.length ? (
<Row className={classes.itemListNot}>
<Col>
<Text>Nothing to show :(</Text>
</Col>
</Row>
) : (
<>
{suggestions.map(({ name, flags }: Country, index) => (
<Row
key={index}
className={`${classes.itemList} ${
index === activeSuggestion - 1 ? classes.activeItem : ""
}`}
onClick={() => handleClick(name.common)}
>
<Col>
<User src={flags.svg} name={name.common} squared />
</Col>
</Row>
))}
</>
)}
</Card.Body>
</Card>
<Text size="$xs">Country selected: {selectedSuggestion}</Text>
</div>
);
};
export default Autocomplete;
Приложение выглядит так:
Заключение
Мы создали простой компонент поиска, применяя фильтры к полученным данным. Этот поиск может усложняться в зависимости от случая. После захвата выбранного значения можно добавить дополнительные функции, например отобразить сведения о выбранной стране.
Я надеюсь, что это руководство было для вас полезным и что вы узнали что-то новое при разработке этого приложения.
Первоначально опубликовано здесь.
Оригинал