React интеграция

Popper.js — это мощная библиотека для управления позиционированием всплывающих элементов относительно целевых элементов на странице. В контексте React её использование требует правильного управления состоянием компонентов и ссылками на DOM-элементы. Основной подход строится вокруг Ref API и хуков React.


Основы работы с Popper.js в React

Popper.js создаёт объект поппера через функцию createPopper(reference, popper, options), где:

  • reference — DOM-элемент, относительно которого позиционируется всплывающий элемент.
  • popper — DOM-элемент, который будет всплывать.
  • options — конфигурация позиционирования, включая стратегии, смещения и модификаторы.

В React прямой доступ к DOM осуществляется через useRef:

import { useRef, useEffect, useState } from 'react';
import { createPopper } from '@popperjs/core';

function Tooltip({ text }) {
  const referenceRef = useRef(null);
  const popperRef = useRef(null);
  const [popperInstance, setPopperInstance] = useState(null);

  useEffect(() => {
    if (referenceRef.current && popperRef.current) {
      const instance = createPopper(referenceRef.current, popperRef.current, {
        placement: 'top',
        modifiers: [{ name: 'offset', options: { offset: [0, 8] } }],
      });
      setPopperInstance(instance);
      return () => instance.destroy();
    }
  }, []);

  return (
    <>
      <button ref={referenceRef}>Наведи на меня</button>
      <div ref={popperRef} style={{ visibility: 'hidden' }}>{text}</div>
    </>
  );
}

Ключевые моменты:

  • useRef хранит ссылки на DOM-элементы, которые необходимы Popper.js.
  • useEffect обеспечивает создание и уничтожение Popper-инстанса при монтировании и размонтировании компонента.
  • visibility: hidden или управление состоянием отображения позволяет контролировать видимость всплывающего элемента.

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

Для динамических тултипов или всплывающих меню часто используют useState для отслеживания видимости:

const [visible, setVisible] = useState(false);

<button
  ref={referenceRef}
  onMouseEn ter={() => setVisible(true)}
  onMouseLe ave={() => setVisible(false)}
>
  Наведи на меня
</button>

<div
  ref={popperRef}
  style={{ visibility: visible ? 'visible' : 'hidden' }}
>
  {text}
</div>

Такой подход позволяет интегрировать Popper.js с любыми интерактивными элементами, сохраняя реактивность состояния React.


Использование модификаторов

Popper.js поддерживает модификаторы, которые позволяют гибко настраивать поведение поппера:

  • offset — смещение по горизонтали и вертикали.
  • flip — автоматическое изменение позиции при недостатке места.
  • preventOverflow — предотвращает выход поппера за границы контейнера.
  • arrow — позиционирование стрелки относительно reference-элемента.

Пример с стрелкой:

<div ref={popperRef} style={{ visibility: visible ? 'visible' : 'hidden' }}>
  <div ref={arrowRef} data-popper-arrow></div>
  {text}
</div>

createPopper(referenceRef.current, popperRef.current, {
  placement: 'top',
  modifiers: [
    { name: 'offset', options: { offset: [0, 10] } },
    { name: 'arrow', options: { element: arrowRef.current } },
    { name: 'flip', options: { fallbackPlacements: ['bottom', 'right', 'left'] } }
  ],
});

Использование data-popper-arrow автоматически учитывает размеры стрелки при позиционировании.


Поппер в функциональных компонентах с хуком

Для удобства можно создать кастомный хук, который инкапсулирует логику Popper.js:

import { useRef, useEffect, useState } from 'react';
import { createPopper } from '@popperjs/core';

export function usePopper(options) {
  const referenceRef = useRef(null);
  const popperRef = useRef(null);
  const [instance, setInstance] = useState(null);

  useEffect(() => {
    if (referenceRef.current && popperRef.current) {
      const popperInstance = createPopper(referenceRef.current, popperRef.current, options);
      setInstance(popperInstance);
      return () => popperInstance.destroy();
    }
  }, [options]);

  return { referenceRef, popperRef, instance };
}

Использование хука в компоненте:

function Tooltip({ text }) {
  const { referenceRef, popperRef } = usePopper({
    placement: 'bottom',
    modifiers: [{ name: 'offset', options: { offset: [0, 12] } }],
  });
  const [visible, setVisible] = useState(false);

  return (
    <>
      <button
        ref={referenceRef}
        onMouseEn ter={() => setVisible(true)}
        onMouseLe ave={() => setVisible(false)}
      >
        Наведи на меня
      </button>
      <div ref={popperRef} style={{ visibility: visible ? 'visible' : 'hidden' }}>
        {text}
      </div>
    </>
  );
}

Такой подход минимизирует повторение кода и облегчает поддержку больших проектов.


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

Popper.js автоматически отслеживает размеры reference и popper, но в React часто требуется принудительно обновлять позицию при изменении состояния:

useEffect(() => {
  if (instance) {
    instance.update();
  }
}, [visible, text]);

Это особенно важно, если контент поппера динамический или изменяется размер кнопки/ссылки.


Работа с порталами (React Portal)

Для всплывающих элементов, которые должны отображаться поверх всего интерфейса, удобно использовать React Portal:

import { createPortal } from 'react-dom';

{visible && createPortal(
  <div ref={popperRef}>{text}</div>,
  document.body
)}

Popper.js корректно позиционирует элементы вне DOM-иерархии родительского компонента, если передать ссылку на DOM в createPopper.


Ключевые рекомендации

  • Всегда использовать Refs для DOM-элементов, на которых строится Popper.
  • Контролировать видимость через состояние React для динамических интерфейсов.
  • Применять модификаторы для адаптивного позиционирования и взаимодействия со стрелками.
  • Обновлять позицию Popper при изменении размеров или контента.
  • При необходимости использовать React Portal для управления z-index и перекрытием элементов.

Эти подходы обеспечивают надёжную и производительную интеграцию Popper.js в проекты на React, сохраняя декларативный стиль и реактивность компонентов.