Динамическое создание и удаление

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

Базовая модель работы строится вокруг создания экземпляра через flatpickr(element, options). При статической разметке элемент уже присутствует в DOM, однако в динамических интерфейсах элемент может появляться после рендера.

import flatpickr from "flatpickr";

function attachDatepicker(input) {
  return flatpickr(input, {
    dateFormat: "Y-m-d",
    allowInput: true
  });
}

При создании элементов на лету важно учитывать момент инициализации: экземпляр должен создаваться только после вставки узла в DOM.

const container = document.querySelector("#container");

function addInput() {
  const input = document.createElement("input");
  input.type = "text";
  input.className = "date-field";

  container.appendChild(input);

  const fp = flatpickr(input, {
    enableTime: true
  });

  return fp;
}

Каждый вызов создаёт независимый экземпляр с собственной конфигурацией и состоянием.

Хранение ссылок на экземпляры

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

const instances = new Map();

function createField(id) {
  const input = document.createElement("input");
  input.dataset.id = id;

  document.body.appendChild(input);

  const instance = flatpickr(input, {
    defaultDate: new Date()
  });

  instances.set(id, instance);
}

Такой подход позволяет обращаться к конкретному календарю без поиска по DOM.

Удаление экземпляра и освобождение ресурсов

Удаление поля без уничтожения экземпляра приводит к утечкам памяти и сохранению обработчиков событий. Для корректного удаления используется метод destroy().

function removeField(id) {
  const instance = instances.get(id);

  if (instance) {
    instance.destroy();
    instances.delete(id);
  }
}

Метод destroy() выполняет:

  • удаление обработчиков событий
  • очистку внутреннего состояния
  • восстановление исходного DOM-элемента (если применимо)

После вызова объект становится невалидным и не должен использоваться.

Пересоздание экземпляра после изменения DOM

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

function reinit(input, config) {
  if (input._flatpickr) {
    input._flatpickr.destroy();
  }

  return flatpickr(input, config);
}

Внутреннее свойство _flatpickr позволяет получить доступ к привязанному экземпляру без внешнего хранилища.

Условное создание экземпляров

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

function lazyInit(input) {
  let instance = null;

  input.addEventListener("focus", () => {
    if (!instance) {
      instance = flatpickr(input, {
        clickOpens: true
      });
    }
  });
}

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

Удаление и повторное создание в модальных окнах

Модальные окна часто создаются и уничтожаются вместе с содержимым. В таких условиях экземпляры календаря должны уничтожаться при закрытии модального слоя.

let modalInstance = null;

function openModal() {
  const input = document.querySelector("#modal-date");

  modalInstance = flatpickr(input, {
    enableTime: true
  });
}

function closeModal() {
  if (modalInstance) {
    modalInstance.destroy();
    modalInstance = null;
  }

  document.querySelector("#modal").remove();
}

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

Делегирование и динамические списки

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

function addRow(table) {
  const row = document.createElement("tr");

  const cell = document.createElement("td");
  const input = document.createElement("input");

  cell.appendChild(input);
  row.appendChild(cell);
  table.appendChild(row);

  flatpickr(input, {
    dateFormat: "d.m.Y"
  });
}

При удалении строки важно синхронно уничтожать связанный экземпляр.

function removeRow(row) {
  const input = row.querySelector("input");

  if (input && input._flatpickr) {
    input._flatpickr.destroy();
  }

  row.remove();
}

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

Flatpickr не предоставляет полноценного deep-update конфигурации для всех параметров, поэтому распространённой практикой является пересоздание экземпляра.

function updateConfig(input, newConfig) {
  const old = input._flatpickr;

  const selectedDates = old ? old.selectedDates : [];

  if (old) {
    old.destroy();
  }

  const instance = flatpickr(input, {
    ...newConfig,
    defaultDate: selectedDates
  });

  return instance;
}

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

Управление пулом экземпляров

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

class DatepickerPool {
  constructor() {
    this.pool = new WeakMap();
  }

  create(input, config) {
    const instance = flatpickr(input, config);
    this.pool.set(input, instance);
    return instance;
  }

  destroy(input) {
    const instance = this.pool.get(input);

    if (instance) {
      instance.destroy();
      this.pool.delete(input);
    }
  }

  destroyAll() {
    this.pool.forEach((instance, input) => {
      instance.destroy();
    });

    this.pool = new WeakMap();
  }
}

Использование WeakMap позволяет автоматически освобождать память при удалении DOM-узлов сборщиком мусора.

Повторная инициализация при изменении атрибутов

В некоторых интерфейсах параметры передаются через data-* атрибуты. При их изменении требуется пересоздание календаря.

function observeInput(input) {
  const observer = new MutationObserver(() => {
    const mode = input.dataset.mode;

    if (input._flatpickr) {
      input._flatpickr.destroy();
    }

    flatpickr(input, {
      mode
    });
  });

  observer.observe(input, { attributes: true });
}

Такой подход характерен для систем с декларативной конфигурацией.

Очистка перед удалением DOM

При удалении контейнера с несколькими календарями требуется проход по всем дочерним элементам.

function destroyContainer(container) {
  const inputs = container.querySelectorAll("input");

  inputs.forEach(input => {
    if (input._flatpickr) {
      input._flatpickr.destroy();
    }
  });

  container.remove();
}

Игнорирование этого шага приводит к сохранению подписок на события даже после удаления узлов из DOM.

Асинхронное создание после загрузки данных

В SPA часто данные приходят асинхронно, после чего создаются поля.

async function renderForm() {
  const data = await fetch("/api/form").then(r => r.json());

  data.fields.forEach(field => {
    const input = document.createElement("input");
    input.value = field.value;

    document.body.appendChild(input);

    flatpickr(input, {
      defaultDate: field.date
    });
  });
}

Инициализация выполняется строго после завершения рендера DOM-структуры.

Контроль повторной инициализации

Одной из частых ошибок является повторное создание экземпляра на одном и том же элементе. Для предотвращения используется проверка:

function safeInit(input, config) {
  if (input._flatpickr) {
    return input._flatpickr;
  }

  return flatpickr(input, config);
}

Такой контроль предотвращает дублирование обработчиков и конфликт состояния.

Сценарии полной замены интерфейса

При смене маршрута в SPA или перерисовке формы целиком выполняется полный цикл уничтожения и повторного создания.

function rebuildUI(container, config) {
  container.querySelectorAll("input").forEach(input => {
    if (input._flatpickr) {
      input._flatpickr.destroy();
    }
  });

  container.innerHTML = "";

  for (let i = 0; i < 5; i++) {
    const input = document.createElement("input");
    container.appendChild(input);

    flatpickr(input, config);
  }
}

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