Интеграция с React

Интеграция Slim Sel ect с React требует понимания различий между императивной библиотекой управления DOM и декларативной моделью React. Slim Sel ect напрямую работает с DOM-элементами <select>, тогда как React управляет состоянием через виртуальный DOM, что приводит к необходимости аккуратного связывания жизненных циклов компонента и экземпляра Slim Select.


Установка и подключение зависимостей

Библиотека устанавливается через пакетный менеджер:

npm install slim-select

Подключение стилей обязательно, так как Slim Select не предоставляет визуального оформления без CSS:

import 'slim-select/styles';

При использовании сборщиков (Vite, Webpack, CRA) импорт стилей обрабатывается автоматически.


Базовая схема интеграции

Основная идея заключается в инициализации Slim Select внутри useEffect и привязке его к ref:

import { useEffect, useRef } fr om 'react';
import SlimSelect fr om 'slim-select';
import 'slim-select/styles';

export default function SelectComponent() {
  const selectRef = useRef(null);
  const slimRef = useRef(null);

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

    slimRef.current = new SlimSelect({
      select: selectRef.current
    });

    return () => {
      if (slimRef.current) {
        slimRef.current.destroy();
        slimRef.current = null;
      }
    };
  }, []);

  return (
    <sel ect ref={selectRef}>
      <option value="1">Первый</option>
      <option value="2">Второй</option>
    </select>
  );
}

Ключевой момент — обязательное уничтожение экземпляра через destroy() при размонтировании компонента, иначе возникает утечка DOM-обработчиков.


Управляемый компонент (controlled pattern)

React-архитектура предполагает хранение состояния выбора в useState, тогда Slim Sel ect используется как внешний синхронизатор UI.

import { useEffect, useRef, useState } fr om 'react';
import SlimSelect fr om 'slim-select';

export default function ControlledSelect() {
  const selectRef = useRef(null);
  const slimRef = useRef(null);

  const [value, setValue] = useState('2');

  useEffect(() => {
    slimRef.current = new SlimSelect({
      select: selectRef.current,
      events: {
        afterChange: (newValue) => {
          setValue(newValue[0]?.value || '');
        }
      }
    });

    return () => slimRef.current?.destroy();
  }, []);

  useEffect(() => {
    if (slimRef.current) {
      slimRef.current.setSelected(value);
    }
  }, [value]);

  return (
    <sel ect ref={selectRef} defaultValue={value}>
      <option value="1">Первый</option>
      <option value="2">Второй</option>
      <option value="3">Третий</option>
    </select>
  );
}

Особенности синхронизации

  • afterChange служит мостом между Slim Select и React state
  • setSelected() используется для обратной синхронизации
  • defaultValue предотвращает конфликт начальной отрисовки

Динамическое обновление списка опций

Slim Select не отслеживает изменения React-дерева автоматически, поэтому требуется ручное обновление данных.

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

  slimRef.current.setData([
    { text: 'Один', value: '1' },
    { text: 'Два', value: '2' },
    { text: 'Три', value: '3' }
  ]);
}, [options]);

Где options — массив состояния React.


Асинхронная загрузка данных

При работе с API важно избегать гонок состояний и повторной инициализации Slim Select.

useEffect(() => {
  let active = true;

  async function load() {
    const res = await fetch('/api/options');
    const data = await res.json();

    if (!active || !slimRef.current) return;

    slimRef.current.setData(
      data.map(item => ({
        text: item.name,
        value: item.id
      }))
    );
  }

  load();

  return () => {
    active = false;
  };
}, []);

Обработка множественного выбора

Slim Select поддерживает multiple, но React требует корректной синхронизации массива значений.

const [values, setValues] = useState(['1', '3']);

useEffect(() => {
  slimRef.current = new SlimSelect({
    select: selectRef.current,
    events: {
      afterChange: (selected) => {
        setValues(selected.map(v => v.value));
      }
    }
  });

  return () => slimRef.current?.destroy();
}, []);

useEffect(() => {
  slimRef.current?.setSelected(values);
}, [values]);

Проблема повторной инициализации в React 18 StrictMode

В режиме разработки React 18 может дважды вызывать useEffect, что приводит к двойной инициализации Slim Select.

Решение заключается в строгом контроле очистки:

useEffect(() => {
  if (slimRef.current) {
    slimRef.current.destroy();
  }

  slimRef.current = new SlimSelect({
    select: selectRef.current
  });

  return () => {
    slimRef.current?.destroy();
    slimRef.current = null;
  };
}, []);

Интеграция с динамическими компонентами

При условной отрисовке важно учитывать момент появления DOM-элемента:

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

  slimRef.current = new SlimSelect({
    select: selectRef.current
  });

  return () => slimRef.current?.destroy();
}, [enabled]);

Работа с формами React Hook Form

Slim Select часто используется в связке с формами:

import { useForm } fr om 'react-hook-form';

function Form() {
  const { register, setValue } = useForm();

  const selectRef = useRef(null);

  useEffect(() => {
    const slim = new SlimSelect({
      select: selectRef.current,
      events: {
        afterChange: (selected) => {
          setValue('field', selected[0]?.value);
        }
      }
    });

    return () => slim.destroy();
  }, []);

  return (
    <form>
      <sel ect {...register('field')} ref={selectRef}>
        <option value="a">A</option>
        <option value="b">B</option>
      </select>
    </form>
  );
}

SSR и Next.js

Slim Select зависит от window и DOM, поэтому требует клиентской инициализации.

import dynamic fr om 'next/dynamic';

const Select = dynamic(() => import('./SelectComponent'), {
  ssr: false
});

Инициализация выполняется только на клиенте, иначе возникает ошибка гидратации.


Обновление UI без перерендера React

Slim Select может изменять UI независимо от React, что иногда приводит к рассинхронизации. В таких случаях приоритет отдаётся React state как источнику истины, а Slim Select рассматривается как слой отображения.


Уничтожение и очистка ресурсов

Правильная очистка предотвращает накопление обработчиков событий:

return () => {
  if (slimRef.current) {
    slimRef.current.destroy();
    slimRef.current = null;
  }
};

Дополнительно важно сбрасывать ссылки при условной отрисовке компонентов, чтобы исключить повторное использование устаревших DOM-узлов.


Типичные архитектурные ошибки интеграции

  • повторная инициализация без destroy()
  • отсутствие синхронизации state и UI
  • использование Slim Select как единственного источника состояния
  • обновление DOM вне React lifecycle
  • отсутствие защиты от null ref при асинхронной загрузке

Модель взаимодействия React и Slim Select

Архитектурно взаимодействие строится по схеме:

  • React управляет состоянием данных
  • Slim Select управляет визуальным представлением <select>
  • события Slim Select транслируются в state
  • изменения state транслируются обратно в Slim Select через методы API

Такое разделение минимизирует конфликт между виртуальным DOM и прямыми DOM-манипуляциями