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

Базовая интеграция через ref и жизненный цикл компонента

Интеграция с React строится вокруг императивной инициализации экземпляра календаря поверх DOM-узла. React управляет виртуальным DOM, тогда как Flatpickr работает напрямую с реальным DOM, поэтому ключевым элементом становится useRef.

import { useEffect, useRef } from "react";
import flatpickr from "flatpickr";
import "flatpickr/dist/flatpickr.css";

export default function DatePicker() {
  const inputRef = useRef(null);

  useEffect(() => {
    const fp = flatpickr(inputRef.current, {
      dateFormat: "Y-m-d"
    });

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

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

Экземпляр создаётся один раз при монтировании компонента, а при размонтировании уничтожается через destroy, что предотвращает утечки памяти и дублирование обработчиков.


Управляемый и неуправляемый режим

В React существует два подхода: uncontrolled (через DOM) и controlled (через state). Flatpickr изначально ближе к uncontrolled модели, но может быть адаптирован.

Неуправляемый вариант

Состояние React не синхронизируется с календарём напрямую:

useEffect(() => {
  flatpickr(inputRef.current, {
    dateFormat: "d.m.Y"
  });
}, []);

Значение извлекается через DOM или callbacks.

Управляемый вариант

Синхронизация состояния требует явного обновления экземпляра:

const [date, setDate] = useState("");

useEffect(() => {
  const fp = flatpickr(inputRef.current, {
    defaultDate: date,
    onChange: (_, dateStr) => {
      setDate(dateStr);
    }
  });

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

Проблема заключается в том, что изменение date не обновляет Flatpickr автоматически без дополнительной логики.


Синхронизация состояния и обновление значения

Flatpickr не является реактивным, поэтому изменение props требует ручного обновления через API экземпляра:

useEffect(() => {
  if (fpRef.current) {
    fpRef.current.setDate(date, false);
  }
}, [date]);

setDate позволяет обновить UI без повторной инициализации компонента.


Хранение экземпляра через useRef

Экземпляр календаря обычно сохраняется отдельно от DOM-рефа:

const inputRef = useRef(null);
const fpRef = useRef(null);

useEffect(() => {
  fpRef.current = flatpickr(inputRef.current, {
    enableTime: true
  });

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

Такой подход позволяет обращаться к API календаря из других эффектов.


Обновление конфигурации без пересоздания

Некоторые параметры можно изменять динамически через set.

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

  fpRef.current.set("enableTime", true);
  fpRef.current.set("dateFormat", "Y-m-d H:i");
}, []);

Однако не все опции поддерживают динамическое обновление, поэтому часть изменений требует пересоздания экземпляра.


Проблемы повторной инициализации в React Strict Mode

В режиме разработки React Strict Mode вызывает двойное монтирование эффектов. Это приводит к созданию двух экземпляров Flatpickr при неправильной реализации.

Корректное поведение обеспечивается строгим уничтожением:

useEffect(() => {
  const fp = flatpickr(inputRef.current, {});

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

Любые внешние переменные вне эффекта приводят к дублированию инстансов.


Обёртка-компонент для переиспользования

Типовая архитектура предполагает создание переиспользуемого компонента-обёртки:

function FlatpickrInput({ value, onChange, options }) {
  const inputRef = useRef(null);
  const fpRef = useRef(null);

  useEffect(() => {
    fpRef.current = flatpickr(inputRef.current, {
      ...options,
      defaultDate: value,
      onChange: (_, dateStr) => onChange(dateStr)
    });

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

  useEffect(() => {
    fpRef.current?.setDate(value, false);
  }, [value]);

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

Такой слой инкапсулирует взаимодействие React и Flatpickr и скрывает императивную природу библиотеки.


Поддержка TypeScript

При использовании TypeScript важно явно типизировать ref экземпляра:

import { Instance } from "flatpickr/dist/types/instance";

const fpRef = useRef<Instance | null>(null);

Это позволяет безопасно использовать API методов (setDate, clear, destroy) без приведения типов.


Работа с форматами и локализацией

Конфигурация локалей передаётся через импорт и опцию locale:

import { Russian } from "flatpickr/dist/l10n/ru";

flatpickr(inputRef.current, {
  locale: Russian,
  dateFormat: "d.m.Y"
});

В React-обёртке локаль часто передаётся как prop и требует пересоздания экземпляра при изменении, поскольку динамическое переключение локали не всегда поддерживается корректно.


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

Плагины передаются через массив plugins в конфигурации:

import monthSelectPlugin from "flatpickr/dist/plugins/monthSelect";

flatpickr(inputRef.current, {
  plugins: [new monthSelectPlugin({ shorthand: true })]
});

При React-интеграции плагины должны быть стабильными (мемоизированными), иначе возможна повторная инициализация при каждом рендере.


Оптимизация рендеров и мемоизация конфигурации

Конфигурационный объект рекомендуется фиксировать через useMemo, чтобы избежать лишнего пересоздания инстанса:

const options = useMemo(() => ({
  dateFormat: "Y-m-d",
  enableTime: true
}), []);

Передача нового объекта в useEffect без мемоизации приводит к повторной инициализации календаря.


Очистка и предотвращение утечек памяти

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

return () => {
  fpRef.current?.destroy();
  fpRef.current = null;
};

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


Особенности работы в SSR (Next.js)

В средах серверного рендеринга доступ к window отсутствует, поэтому инициализация должна выполняться только на клиенте:

useEffect(() => {
  if (typeof window === "undefined") return;

  fpRef.current = flatpickr(inputRef.current, {});
}, []);

Также возможна динамическая загрузка библиотеки через import() для уменьшения веса initial bundle.


Интеграция с формами и валидацией

Flatpickr часто используется как замена нативного input type=“date”. При интеграции с библиотеками форм важен контроль значения через onChange и setDate, поскольку прямое изменение DOM-значения не всегда синхронизируется с состоянием формы.

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