Игра Яндекс Практикума
Игра Яндекс Практикума
Игра Яндекс Практикума

Как создать компонент тегов с автозаполнением на React

В этом руководстве мы создадим компонент ввода тегов с автозаполнением, используя React JS без сторонних библиотек.

199 открытий3К показов
Как создать компонент тегов с автозаполнением на React

Это перевод статьи — оригинал взят отсюда.

В этом руководстве мы расскажем, как создать компонент тегов на React Java Script без использования сторонних библиотек. Показываем, как управлять состоянием, динамически фильтровать предложения и эффективно обрабатывать взаимодействия пользователя, такие как добавление и удаление тегов. Идеально подходит для разработчиков, которые хотят реализовать ввод тегов с минимальными зависимостями и гибким дизайном.

Сетап проекта

Мы будем использовать Vite.js для быстрой и простой сборки проекта. Vite обеспечивает мгновенную перезагрузку модулей, улучшенную производительность и более быструю сборку по сравнению с традиционными сборщиками.

Создаем новый проект на базе Vite:

			npm create vite@latest react-tags-autocomplete -- --template react-ts
		

После установки:

			cd react-tags-autocomplete
		
			npm install
		
			npm run dev
		

Очистите проект

Удалите файл App.css. А еще удалите все данные из файла App.tsx:

			function App() {
  return <>;
}
export default App;
		

Теперь создайте папку components внутри папки src.

Создайте компонент тегов

Создайте файл TextInput.tsx в папке компонентов:

			import { ChangeEvent, useState } from 'react';

const TextInput = () => {
  const [tags, setTags] = useState([]);
  const handleKeydown = (e: ChangeEvent & KeyboardEvent) => {
    if (e.key !== 'Enter') {
      return;
    }
    const value = e.target.value;
    if (!value.trim()) {
      return;
    }
    setTags([...tags, value]);
    e.target.value = '';
  };

  const removeTag = (idx) => {
    setTags(tags.filter((el, i) => i !== idx));
  };
  return (
    
      {tags.map((tag, i) => {
        return (
          
            {tag}
             removeTag(i)}>
              ×
            
          
        );
      })}
      
    
  );
};
export default TextInput;
		

Объяснение

  1. Поле ввода захватывает текст пользователя, фильтруя предварительно заданные предложения (массив предложений).
  2. Отфильтрованные предложения отображаются в виде выпадающего списка. Нажатие на предложение или клавишу Enter добавляет его в список тегов.
  3. Добавленные теги отображаются с возможностью их удаления.

Стилизация компонента

Удалите все из файла index.css и вставьте следующий код:

			* {
  margin: 0;
  padding: 0;
}

html,
body {
  height: 100%;
  width: 100%;
}

body {
  display: flex;
  justify-content: center;
  align-items: center;
  font-family: 'Courier New', monospace;
}

label {
  margin-bottom: 4px;
  display: block;
  font-size: 1.125rem;
  line-height: 1.75rem;
}

#root {
  display: flex;
  flex-direction: column;
  justify-content: center;
  max-width: 540px;
  margin-left: auto;
  margin-right: auto;
  padding-left: 1rem;
  padding-right: 1rem;
  margin-top: calc(1.5rem);
  margin-bottom: calc(1.5rem);
  color: #333333;
}

.text-input__wrapper {
  border: 1px solid black;
  padding: 0.5rem;
  border-radius: 3px;
  width: min(80vw, 600px);
  margin-top: 1em;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5em;
}
.tag-item {
  background-color: rgb(218, 216, 216);
  display: inline-block;
  padding: 0.5em 0.75em;
  border-radius: 20px;
}
.tag-item .close {
  width: 20px;
  height: 20px;
  background-color: rgb(48, 48, 48);
  color: #fff;
  border-radius: 50%;
  display: inline-flex;
  justify-content: center;
  align-items: center;
  margin-left: 0.5em;
  font-size: 18px;
}
.text-input {
  padding-left: 1rem;
  padding-right: 1rem;
  padding-top: 0.625rem;
  padding-bottom: 0.625rem;
  font-size: 1.125rem;
  line-height: 1.75rem;
  background-color: #ffffff;
  position: relative;
  flex-grow: 1;
  outline: none;
  border: none;
  outline: none;
}
		

Проверим:

			npm run dev
		

У нас почти готовый компонент для добавления тегов, но в нём есть одна небольшая ошибка. Сейчас можно добавлять дублирующиеся теги в поле ввода. Чтобы это исправить, нам нужно изменить функцию handleKeydown().

			const handleKeydown = (e: ChangeEvent & KeyboardEvent) => {
  if (e.key !== 'Enter') {
    return;
  }
  const value = e.target.value;
  if (!value.trim()) {
    return;
  }

  setTags((tags: string[]) => {
    if (tags.some((tag) => tag.toLowerCase() === value.toLowerCase())) {
      return [...tags];
    } else {
      return [...tags, value];
    }
  });
  e.target.value = '';
};
		

Теперь можно добавлять только уникальные теги.

Создайте компонент автозаполения

Создайте файл AutoComplete.tsx в папке компоненты:

			import React, { ChangeEvent, useState } from 'react';

type AutoCompleteProps = {
  possibleValues: string[];
  handleKeydown: () => void;
  setTags: (values: string[]) => void;
};

function Autocomplete({ possibleValues, handleKeydown, setTags }: AutoCompleteProps) {
  const [inputValue, setInputValue] = useState('');
  const [suggestions, setSuggestions] = useState([]);

  const handleInputChange = (event: ChangeEvent) => {
    const value = event.target.value;

    setInputValue(value);

    if (value.length > 0) {
      const filteredSuggestions = possibleValues.filter((suggestion) =>
        suggestion.toLowerCase().includes(value.toLowerCase()),
      );
      setSuggestions(filteredSuggestions);
    } else {
      setSuggestions([]);
    }
  };

  const handleSuggestionClick = (value: string) => {
    setTags((tags: string[]) => {
      if (tags.some((tag) => tag.toLowerCase() === value.toLowerCase())) {
        return [...tags];
      } else {
        return [...tags, value];
      }
    });

    setSuggestions([]);
    setInputValue('');
  };

  const onKeyDown = (e: ChangeEvent & KeyboardEvent) => {
    handleKeydown(e);
    if (e.key === 'Enter') {
      setInputValue('');
      setSuggestions([]);
    }
  };

  return (
    <>
      
      
        {suggestions.length > 0 && (
          
            {suggestions.map((suggestion, index) => (
               handleSuggestionClick(suggestion)} role='option'>
                {suggestion}
              
            ))}
          
        )}
      
    
  );
}

export default Autocomplete;
		

Ключевые особенности

  • Автозаполнение предложений. Компонент предоставляет список предложений на основе ввода пользователя. Когда пользователь вводит текст в поле, происходит фильтрация возможных значений, и отображаются совпадающие предложения. 
  • Предотвращение дубликатов. Компонент гарантирует, что дублирующиеся теги не будут добавлены. 
  • Навигация с клавиатуры. Функция onKeyDown обрабатывает события нажатия клавиш, в частности предотвращая нежелательное поведение при нажатии клавиши Enter.

Разбор кода

Управление состоянием:

  • inputValue  — отслеживает текст, который пользователь вводит в поле ввода. 
  • suggestions — массив отфильтрованных возможных значений, которые совпадают с вводом пользователя.
			const [inputValue, setInputValue] = useState('');
const [suggestions, setSuggestions] = useState([]);
		

Обработка изменений ввода

  • handleInputChange — обновляет значение inputValue на основе ввода пользователя и генерирует предложения, фильтруя possibleValues. 
  • possibleValues — массив строк, переданный как пропс. Фильтр проверяет, соответствует ли ввод одному из значений в possibleValues (без учета регистра).
			const handleInputChange = (event: ChangeEvent) => {
  const value = event.target.value;
  setInputValue(value);

  if (value.length > 0) {
    const filteredSuggestions = possibleValues.filter((suggestion) =>
      suggestion.toLowerCase().includes(value.toLowerCase()),
    );
    setSuggestions(filteredSuggestions);
  } else {
    setSuggestions([]);
  }
};
		

Обработка кликов по предложениям

Когда пользователь нажимает на предложение, вызывается функция handleSuggestionClick.

Эта функция добавляет выбранное предложение в теги (управляется родительским компонентом через setTags), если оно еще не было добавлено.

После добавления тега функция очищает предложения и сбрасывает значение inputValue.

			const handleSuggestionClick = (value: string) => {
  setTags((tags: string[]) => {
    if (tags.some((tag) => tag.toLowerCase() === value.toLowerCase())) {
      return [...tags]; // No duplicate tags
    } else {
      return [...tags, value]; // Add the new tag
    }
  });

  setSuggestions([]);
  setInputValue('');
};
		

Обработка ввода с клавиатуры

onKeyDown обрабатывает события нажатия клавиш, особенно Enter.

При нажатии Enter происходит очистка как поля ввода, так и предложений.

Также вызывается функция handleKeydown, переданная из родительского компонента, для дальнейшей настройки или обработки.

			const onKeyDown = (e: ChangeEvent & KeyboardEvent) => {
  handleKeydown(e);
  if (e.key === 'Enter') {
    setInputValue('');
    setSuggestions([]);
  }
};
		

Отображение

Компонент состоит из поля ввода и списка предложений. Поле ввода обновляет значение inputValue и запускает фильтрацию предложений, а список отображает отфильтрованные варианты. Когда есть совпадающие предложения, компонент отображает список опций. Нажатие на вариант добавляет его в теги.

			return (
  <>
    
    
      {suggestions.length > 0 && (
        
          {suggestions.map((suggestion, index) => (
             handleSuggestionClick(suggestion)} role='option'>
              {suggestion}
            
          ))}
        
      )}
    
  
);
		

Пропсы:

  • possibleValues — массив строк, которые выступают в качестве потенциальных предложений для автозаполнения. 
  • handleKeydown — функция, переданная из родительского компонента для обработки событий нажатия клавиш. 
  • setTags — функция, которая обновляет список тегов при выборе предложения. 

Нам просто нужно заменить поле ввода на наш кастомный компонент AutoComplete и передать ему все необходимые пропсы.

Добавьте стили в index.css:

			.autocomplete-wrapper {
  width: 100%;
}

.suggestions-list {
  top: 100%;
  border: 1px solid #ccc;
  background: white;
  list-style: none;
  padding: 0;
  margin: 0;
  border-radius: 3px;
}

.suggestions-list li {
  padding: 8px;

  cursor: pointer;
}

.suggestions-list li:hover {
  background-color: #e9e9e9;
}
		

Давайте тестить:

			npm run dev
		

Осталась только одна проблема: мы теряем фокус после добавления тега через предложение. Чтобы исправить это, нам нужно передать ref в поле ввода и вручную установить фокус.

Использование ссылок (refs) для доступа к значениям

Определите ref в начале компонента AutoComplete:

			const inputRef = useRef(null);
		

Затем передайте его в тег input:

			ref = { inputRef };
		

Теперь, в самом конце функций handleSuggestionClick() и onKeyDown() (после выполнения всего кода), добавьте строку:

			inputRef.current.focus();
		

Заключение:

Этот компонент позволяет пользователям вводить текст и выбирать варианты из отфильтрованных предложений. Он предотвращает дублирование записей, обрабатывает события нажатия клавиш и эффективно управляет внутренним состоянием ввода и предложений.

Вы также можете просмотреть весь код на GitHub Gist: AutoComplete.tsx | TextInput.tsx.

Следите за новыми постами
Следите за новыми постами по любимым темам
199 открытий3К показов