Использование с React

Библиотека Awesomplete изначально разработана как лёгкий независимый модуль для работы с автодополнением в обычном DOM-окружении. В контексте React она не теряет своей ценности, но требует правильного способа интеграции, поскольку React управляет DOM декларативно, а Awesomplete — императивно.

Ключевая задача при использовании Awesomplete в React — синхронизировать жизненный цикл компонента с инициализацией, обновлением и уничтожением экземпляра автодополнения.


Базовая интеграция через useRef и useEffect

React-хук useRef используется для получения прямого доступа к DOM-элементу input, а useEffect — для создания и управления экземпляром Awesomplete.

import React, { useEffect, useRef } from "react";
import Awesomplete from "awesomplete";
import "awesomplete/awesomplete.css";

function AutocompleteInput({ list }) {
  const inputRef = useRef(null);
  const awesompleteRef = useRef(null);

  useEffect(() => {
    if (!inputRef.current) return;

    awesompleteRef.current = new Awesomplete(inputRef.current, {
      list: list,
      minChars: 1,
      maxItems: 10,
      autoFirst: true
    });

    return () => {
      awesompleteRef.current = null;
    };
  }, []);

  return <input ref={inputRef} />;
}

Важный момент заключается в том, что Awesomplete не знает о React-рендере. Поэтому инициализация происходит только после монтирования DOM-элемента.


Обновление списка данных при изменении props

Частая проблема — динамическое обновление списка подсказок. Awesomplete не реагирует автоматически на изменение props, поэтому необходимо вручную обновлять list.

useEffect(() => {
  if (awesompleteRef.current) {
    awesompleteRef.current.list = list;
  }
}, [list]);

Такой подход позволяет синхронизировать внешний источник данных с внутренним состоянием автодополнения без пересоздания экземпляра.


Контролируемый input и Awesomplete

React часто использует контролируемые компоненты, где значение input хранится в state. Awesomplete может конфликтовать с таким подходом, если не учитывать порядок обновлений.

import React, { useState, useRef, useEffect } from "react";
import Awesomplete from "awesomplete";

function ControlledAutocomplete({ suggestions }) {
  const [value, setValue] = useState("");
  const inputRef = useRef(null);
  const awesompleteRef = useRef(null);

  useEffect(() => {
    if (!inputRef.current) return;

    awesompleteRef.current = new Awesomplete(inputRef.current, {
      list: suggestions
    });

    inputRef.current.addEventListener("awesomplete-selectcomplete", (e) => {
      setValue(e.text.value);
    });

    return () => {
      awesompleteRef.current = null;
    };
  }, [suggestions]);

  return (
    <input
      ref={inputRef}
      value={value}
      onCha nge={(e) => setValue(e.target.value)}
    />
  );
}

Здесь важен момент: событие awesomplete-selectcomplete используется для синхронизации выбора с React state.


Предотвращение конфликтов между React и DOM-манипуляциями

Awesomplete напрямую изменяет DOM, добавляя список подсказок рядом с input. React при этом не контролирует эти элементы.

Чтобы избежать конфликтов:

  • не перерисовывать input без необходимости
  • не использовать conditional rendering, который удаляет input из DOM при каждом обновлении
  • избегать повторной инициализации Awesomplete без очистки предыдущего экземпляра

Пример правильного управления жизненным циклом:

useEffect(() => {
  if (!inputRef.current) return;

  const instance = new Awesomplete(inputRef.current, { list: suggestions });
  awesompleteRef.current = instance;

  return () => {
    awesompleteRef.current = null;
  };
}, []);

Программное управление открытием и закрытием списка

Awesomplete предоставляет методы управления поведением выпадающего списка, которые можно использовать внутри React-логики.

awesompleteRef.current.open();
awesompleteRef.current.close();
awesompleteRef.current.evaluate();

В React это часто связывается с дополнительными эффектами:

useEffect(() => {
  if (value.length >= 2) {
    awesompleteRef.current?.evaluate();
  }
}, [value]);

Такой подход позволяет вручную контролировать момент появления подсказок.


Асинхронные источники данных

При работе с API список подсказок часто формируется динамически. Awesomplete поддерживает функцию вместо массива.

useEffect(() => {
  if (!inputRef.current) return;

  awesompleteRef.current = new Awesomplete(inputRef.current, {
    list: (text, callback) => {
      fetch(`/api/suggest?q=${text}`)
        .then(res => res.json())
        .then(data => callback(data));
    }
  });
}, []);

Этот режим позволяет реализовать серверное автодополнение без дополнительной логики в React.


Оптимизация и предотвращение лишних ререндеров

При частых изменениях списка или значения input важно избегать пересоздания Awesomplete.

Практика:

  • хранить экземпляр в useRef
  • обновлять только list
  • не привязывать инициализацию к часто меняющимся props
useEffect(() => {
  if (awesompleteRef.current) {
    awesompleteRef.current.list = suggestions;
  }
}, [suggestions]);

Это снижает нагрузку и исключает мерцание UI.


Использование кастомных событий Awesomplete в React

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

Основные события:

  • awesomplete-select
  • awesomplete-selectcomplete
  • awesomplete-open
  • awesomplete-close

Пример обработки:

useEffect(() => {
  const input = inputRef.current;
  if (!input) return;

  const handler = (e) => {
    console.log("Выбран элемент:", e.text.value);
  };

  input.addEventListener("awesomplete-selectcomplete", handler);

  return () => {
    input.removeEventListener("awesomplete-selectcomplete", handler);
  };
}, []);

Такой механизм позволяет интегрировать Awesomplete в архитектуру событий React без потери контроля.


Инкапсуляция в переиспользуемый компонент

Для масштабируемых приложений логично выделить обёртку над Awesomplete.

function useAwesomplete(inputRef, options) {
  const instanceRef = useRef(null);

  useEffect(() => {
    if (!inputRef.current) return;

    instanceRef.current = new Awesomplete(inputRef.current, options);

    return () => {
      instanceRef.current = null;
    };
  }, []);

  useEffect(() => {
    if (instanceRef.current && options.list) {
      instanceRef.current.list = options.list;
    }
  }, [options.list]);

  return instanceRef;
}

Такой хук упрощает повторное использование логики автодополнения в разных компонентах.


Особенности работы с StrictMode

React StrictMode в режиме разработки может вызывать двойное монтирование компонентов. Это приводит к повторной инициализации Awesomplete, если не предусмотрена очистка.

Корректное управление:

  • всегда реализовывать cleanup-функцию в useEffect
  • не полагаться на однократную инициализацию без защиты

Типовые архитектурные ошибки

  • создание Awesomplete внутри рендера компонента
  • отсутствие cleanup при размонтировании
  • хранение экземпляра в state вместо ref
  • смешивание uncontrolled и controlled input без синхронизации
  • пересоздание экземпляра при каждом изменении props

Такие ошибки приводят к утечкам памяти, дублированию DOM-элементов и неконсистентному поведению автодополнения.


Поведение при SSR (Server-Side Rendering)

Awesomplete работает только в браузерной среде. При использовании SSR (например, в Next.js) требуется условная инициализация:

useEffect(() => {
  if (typeof window === "undefined") return;
  if (!inputRef.current) return;

  awesompleteRef.current = new Awesomplete(inputRef.current, {
    list: suggestions
  });
}, []);

Это предотвращает ошибки гидратации и обращения к document на сервере.