FormData API

FormData представляет собой встроенный механизм браузера для формирования и передачи данных формы в виде пар ключ–значение. Объект ориентирован на сценарии отправки данных через fetch или XMLHttpRequest без необходимости ручной сериализации в JSON.

Основные особенности:

  • хранит данные в виде пар name → value
  • поддерживает строки и бинарные данные (File, Blob)
  • автоматически совместим с multipart/form-data
  • допускает повторяющиеся ключи (важно для multi-select)

Базовая инициализация:

const form = document.querySelector('form');
const formData = new FormData(form);

Добавление значений вручную:

const formData = new FormData();
formData.append('username', 'alex');
formData.append('role', 'admin');

Извлечение данных:

formData.get('username');
formData.getAll('role');

Slim Select и специфика сериализации значений

Библиотека Slim Select заменяет стандартный <select> кастомным UI-компонентом, сохраняя при этом исходный элемент в DOM. Это создаёт ключевую особенность: визуальное состояние отделено от нативной формы.

Slim Select работает поверх <select>, но фактическая отправка данных через FormData зависит от синхронизации состояния компонента с DOM-элементом.

Основная проблема:

  • пользователь взаимодействует с кастомным UI
  • FormData читает только DOM <select>
  • требуется синхронизация значений

Получение значений Slim Select

Slim Select предоставляет API для доступа к выбранным значениям:

const select = new SlimSelect({
  select: '#mySelect'
});

const values = select.getSelected();

Возвращаемые данные:

  • для single select: строка
  • для multiple select: массив строк

Пример:

["js", "ts", "node"]

Однако FormData сам по себе не знает о Slim Select, поэтому необходимо обеспечить актуальность <select>.


Синхронизация Slim Select с DOM <select>

Slim Select автоматически обновляет оригинальный <select>, устанавливая selected у <option>.

Пример структуры:

<select id="mySelect" name="skills" multiple>
  <option value="js">JavaScript</option>
  <option value="ts">TypeScript</option>
  <option value="node">Node.js</option>
</select>

После выбора:

<option value="js" selected></option>
<option value="ts" selected></option>

Следовательно:

const formData = new FormData(form);

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


Multi-select и FormData: поведение повторяющихся ключей

При multiple HTML-элементе FormData автоматически создаёт несколько записей с одинаковым ключом.

Пример:

<select name="skills" multiple>
const formData = new FormData(form);

Результат внутри FormData:

  • skills = js
  • skills = ts
  • skills = node

Извлечение:

formData.getAll('skills');
// ["js", "ts", "node"]

Это поведение критично для Slim Select, поскольку он чаще всего используется именно для multi-select интерфейсов.


Ручное формирование FormData на основе Slim Select

В некоторых сценариях требуется игнорировать DOM и формировать данные напрямую из API Slim Select.

const select = new SlimSelect({
  select: '#mySelect'
});

const formData = new FormData();

const values = select.getSelected();

values.forEach(value => {
  formData.append('skills', value);
});

Такой подход применяется, когда:

  • select не является частью формы
  • данные собираются динамически
  • требуется контроль структуры payload

Использование скрытых полей для полной синхронизации

Альтернативная архитектура основана на hidden input, который синхронизируется со Slim Select.

<input type="hidden" name="skills" id="skillsHidden">
const select = new SlimSelect({
  select: '#mySelect',
  onChange: (info) => {
    const hidden = document.querySelector('#skillsHidden');
    hidden.value = info.map(i => i.value).join(',');
  }
});

В этом случае FormData читает одно поле:

const formData = new FormData(form);
formData.get('skills'); // "js,ts,node"

Недостаток подхода — потеря нативной структуры multi-value.


Формирование структурированных данных (JSON через FormData)

FormData не ограничивает хранение строк. Часто применяется сериализация сложных объектов.

const values = select.getSelected();

const formData = new FormData();
formData.append('skills', JSON.stringify(values));

Пример структуры:

[
  { "text": "JavaScript", "value": "js" },
  { "text": "TypeScript", "value": "ts" }
]

На сервере выполняется:

JSON.parse(formData.get('skills'));

Отправка данных через fetch

Стандартный сценарий интеграции Slim Select с FormData:

const form = document.querySelector('form');

form.addEventListener('submit', async (e) => {
  e.preventDefault();

  const formData = new FormData(form);

  await fetch('/api/submit', {
    method: 'POST',
    body: formData
  });
});

Особенности:

  • заголовок Content-Type не устанавливается вручную
  • браузер сам формирует multipart/form-data
  • Slim Select не требует дополнительных преобразований при корректной синхронизации DOM

Динамическое обновление FormData при изменении Slim Select

В сложных интерфейсах FormData формируется не только при submit, но и в процессе работы формы.

const select = new SlimSelect({
  select: '#mySelect',
  onChange: () => {
    const formData = buildFormData();
    console.log([...formData.entries()]);
  }
});

function buildFormData() {
  const formData = new FormData();

  const values = select.getSelected();
  values.forEach(v => formData.append('skills', v));

  return formData;
}

Такой подход применяется в:

  • SPA-формах
  • live-preview
  • автосохранении

Особенности удаления значений и очистки FormData

FormData не имеет метода прямого удаления по индексу массива значений, поэтому пересборка является стандартным решением:

function rebuild(values) {
  const fd = new FormData();

  values.forEach(v => fd.append('skills', v));

  return fd;
}

Slim Select упрощает это через:

select.setSelected([]);

Согласованность типов данных

Slim Select может возвращать:

  • строки
  • массив строк
  • массив объектов (в зависимости от конфигурации)

FormData же всегда работает с примитивными значениями.

Следствие:

  • объекты требуют сериализации
  • массивы требуют либо multiple append, либо JSON

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

formData.append('skills', { value: 'js' }); // будет "[object Object]"

Корректный вариант:

formData.append('skills', JSON.stringify({ value: 'js' }));

Частые ошибки интеграции Slim Select и FormData

1. Отсутствие синхронизации с <select>

Slim Select создан, но <option> не обновляются из-за кастомной инициализации.

Результат:

new FormData(form).getAll('skills'); // []

2. Использование select вне form

<select id="mySelect"></select>

без <form> приводит к пустому FormData при стандартной инициализации.

Решение — ручная сборка.


3. Потеря multiple значений при hidden input

hidden.value = "js,ts,node";

На сервере это уже строка, а не массив, что ломает типизацию данных.


4. Несоответствие name атрибута

FormData полностью зависит от:

name="skills"

Отсутствие name делает поле невидимым для FormData.


Поведение при асинхронных изменениях Slim Select

Slim Select может обновляться динамически:

select.setData([
  { text: 'Go', value: 'go' }
]);

FormData будет корректен только после обновления DOM <select> или повторной синхронизации.


Работа с серверными API и multipart payload

FormData с Slim Select чаще всего используется в:

  • загрузке профилей пользователя
  • фильтрации каталога
  • многошаговых формах

Пример payload:

skills=js
skills=ts
skills=node
username=alex

Slim Select обеспечивает только UI-уровень, вся ответственность за структуру данных ложится на FormData и HTML-разметку.