Проблемы с событиями

В Flatpickr события строятся вокруг экземпляра календаря и его жизненного цикла. Основные коллбеки передаются в конфигурации при инициализации и привязываются к конкретному экземпляру, а не к DOM-элементу напрямую. Это ключевой момент, из которого вытекает большинство проблем при интеграциях.

События в Flatpickr делятся на два типа:

  • Конфигурационные коллбеки (onChange, onOpen, onClose, onReady и др.)
  • DOM-события input’а, которые часто путаются с внутренними событиями библиотеки

Flatpickr не является «обёрткой над input change», он заменяет поведение поля ввода собственным состоянием. Поэтому стандартные браузерные события и внутренние события библиотеки могут расходиться по времени и смыслу.


Перезапись событий при повторной инициализации

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

При повторном вызове flatpickr(element, config) предыдущие обработчики не всегда корректно очищаются, особенно если ссылка на экземпляр не сохраняется и не вызывается destroy().

Типичный эффект:

  • события срабатывают несколько раз
  • onChange вызывается дублированно
  • появляются «призрачные» обработчики из старого экземпляра

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

Правильная модель поведения всегда требует хранения ссылки:

const fp = flatpickr("#date", {
  onChange: () => {}
});

// при пересоздании
fp.destroy();

Потеря контекста this внутри событий

Flatpickr передаёт контекст экземпляра в this, но только при использовании обычных функций.

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

onChange: (selectedDates) => {
  console.log(this); // undefined или внешний контекст
}

Правильное поведение:

onChange: function(selectedDates) {
  console.log(this); // экземпляр flatpickr
}

Это критично, поскольку через this доступна внутренняя модель:

  • this.selectedDates
  • this.currentMonth
  • this.setDate()
  • this.input

Нарушение контекста приводит к тому, что код начинает работать «вслепую» без доступа к состоянию календаря.


Конфликт между onChange и setDate

Метод setDate() часто вызывает цепочку событий, которая воспринимается как ошибка.

Поведение зависит от параметров:

  • triggerChange = true — события вызываются
  • triggerChange = false — события подавляются

Проблема возникает при циклических обновлениях:

fp.setDate("2026-01-01");
fp.setDate("2026-01-02");

Каждый вызов может триггерить onChange, что приводит к:

  • бесконечным обновлениям UI
  • повторным запросам к API
  • каскадным эффектам в реактивных системах

Типичный защитный паттерн — разделение «внутреннего» и «пользовательского» обновления:

fp.setDate(date, false);

Дублирование событий в SPA-фреймворках

При использовании Flatpickr в React/Vue/Angular возникает проблема повторной подписки.

Причина — жизненный цикл компонента:

  • компонент монтируется → инициализация flatpickr
  • компонент обновляется → повторная инициализация
  • старый экземпляр не уничтожен → события дублируются

В результате:

  • onChange срабатывает N раз
  • обработчики API вызываются многократно
  • состояние формы становится неконсистентным

Ключевой источник проблемы — отсутствие destroy() при unmount.


Конфликт нативных событий input и событий Flatpickr

Flatpickr управляет значением input вручную, поэтому нативные события input и change могут не соответствовать реальному состоянию календаря.

Распространённый сценарий ошибки:

input.addEventListener("change", () => {
  // ожидание актуальной даты
});

Но Flatpickr обновляет значение:

  • либо асинхронно
  • либо после внутренних вычислений
  • либо только при закрытии календаря

В результате нативное событие может сработать:

  • раньше onChange
  • позже onClose
  • или вообще не отражать выбранную дату

Порядок вызова событий и скрытые зависимости

Flatpickr имеет внутренний порядок событий, который не всегда очевиден:

  • onReady
  • onOpen
  • onMonthChange
  • onYearChange
  • onValueUpdate
  • onChange
  • onClose

Проблема возникает, когда логика завязана на «предполагаемый порядок», например:

  • ожидание, что onChange всегда будет последним
  • или что onClose всегда следует после onChange

В реальности порядок может меняться при:

  • programmatic setDate
  • переключении месяцев
  • вводе вручную
  • мобильных устройствах

Асинхронные обновления и рассинхронизация состояния

Flatpickr не всегда синхронно обновляет внутренние поля и input. Это приводит к состояниям гонки:

onChange: function() {
  console.log(this.input.value);
}

Иногда значение input.value ещё не обновлено в момент вызова события.

Это особенно заметно при:

  • быстром выборе дат
  • использовании range mode
  • кастомных плагинах

Результат — «устаревшие» значения в обработчиках.


Проблемы с множественными обработчиками onChange

Flatpickr допускает несколько способов навешивания событий:

  • через конфиг (onChange)
  • через instance.config.onChange.push()

Проблема в том, что второй способ добавляет обработчики, а не заменяет их:

fp.config.onChange.push(fn);

В результате:

  • накапливаются обработчики
  • логика дублируется
  • порядок вызова становится непредсказуемым

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


Потеря событий при destroy и повторной инициализации

Метод destroy() не всегда гарантирует полное удаление всех внешних ссылок, если:

  • сохранены внешние ссылки на fp
  • остались подписки в замыканиях
  • используются глобальные обработчики

Типичный эффект:

  • старый календарь уже уничтожен
  • но onChange продолжает срабатывать

Причина — утечки через внешние ссылки, а не через сам Flatpickr.


Проблемы с мобильными событиями

На мобильных устройствах Flatpickr часто переключается в альтернативный режим ввода.

Это влияет на события:

  • onOpen может не сработать при программном вызове
  • onClose зависит от системной клавиатуры
  • onChange может срабатывать только при blur

В результате логика, завязанная на события, ведёт себя по-разному на десктопе и мобильных устройствах.


Взаимодействие событий с режимами range и multiple

В режимах range и multiple события становятся более «шумными»:

  • onChange вызывается при каждом клике
  • промежуточные состояния считаются валидными
  • массив selectedDates изменяется постепенно

Проблема возникает, когда обработчик ожидает финальное значение:

onChange: function(dates) {
  // предполагается, что диапазон уже завершён
}

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


Изоляция событий и stopPropagation

Flatpickr не всегда блокирует всплытие событий DOM-элемента. Это приводит к конфликтам с родительскими обработчиками:

  • клики по календарю вызывают внешние listeners
  • закрытие календаря триггерит глобальные handlers
  • формы могут сабмититься случайно

Часто требуется дополнительная изоляция:

  • блокировка click на контейнере
  • контроль stopPropagation в кастомных плагинах
  • разделение зон клика

Нестабильность событий при динамических стилях и пересчёте позиции

При изменении DOM или CSS во время работы календаря (например, смена темы или скрытие блоков) могут происходить побочные эффекты:

  • onOpen вызывается повторно
  • onClose срабатывает без действия пользователя
  • календарь пересчитывает позицию и триггерит внутренние события

Это связано с тем, что Flatpickr реагирует на изменения layout и пересчитывает popper-позиционирование, что косвенно влияет на жизненный цикл событий.