Интеграция с 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 без повторной
инициализации компонента.
Экземпляр календаря обычно сохраняется отдельно от 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 вызывает двойное монтирование эффектов. Это приводит к созданию двух экземпляров 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 важно явно типизировать 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 и требует пересоздания экземпляра при изменении, поскольку динамическое переключение локали не всегда поддерживается корректно.
Плагины передаются через массив 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;
};
Особенно критично при использовании условного рендеринга, где компонент может часто размонтироваться и монтироваться заново.
В средах серверного рендеринга доступ к window
отсутствует, поэтому инициализация должна выполняться только на
клиенте:
useEffect(() => {
if (typeof window === "undefined") return;
fpRef.current = flatpickr(inputRef.current, {});
}, []);
Также возможна динамическая загрузка библиотеки через
import() для уменьшения веса initial bundle.
Flatpickr часто используется как замена нативного input type=“date”.
При интеграции с библиотеками форм важен контроль значения через
onChange и setDate, поскольку прямое изменение
DOM-значения не всегда синхронизируется с состоянием формы.
Особенность заключается в том, что значение хранится в строковом формате, и преобразование даты должно выполняться на уровне формы, а не внутри календаря.