Урок 06

Работа с FormData

FormData — встроенный браузерный API для сбора данных формы. Удобен, когда полей много или есть файлы.

Базовое использование

function Form() {
    function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
        e.preventDefault();

        const data = new FormData(e.currentTarget);

        console.log(data.get('email'));
        console.log(data.get('password'));
    }

    return (
        <form onSubmit={handleSubmit}>
            <input name="email" type="email" />
            <input name="password" type="password" />
            <button>Войти</button>
        </form>
    );
}

e.currentTarget — форма. Поля собираются по имени name.

Методы

const data = new FormData();

data.append('key', 'value');
data.append('file', fileInput.files[0]);

data.get('key');        // значение
data.getAll('key');     // все значения
data.has('key');        // есть ли ключ
data.delete('key');
data.set('key', 'new'); // заменяет

Отправка на сервер

async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();

    const data = new FormData(e.currentTarget);

    const res = await fetch('/api/posts', {
        method: 'POST',
        body: data,
    });
}

Не устанавливай Content-Type вручную — fetch сам подставит multipart/form-data с boundary.

Файлы

<form onSubmit={handleSubmit}>
    <input name="title" />
    <input type="file" name="cover" />
    <button>Загрузить</button>
</form>
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();

    const data = new FormData(e.currentTarget);

    await fetch('/api/upload', { method: 'POST', body: data });
}

Несколько файлов

<input type="file" name="files" multiple />

const files = data.getAll('files');

JSON vs FormData

JSON — для API с Content-Type: application/json. Удобно, когда данные простые.

FormData — для форм с файлами и когда сервер ожидает multipart/form-data.

Преобразование в объект

const data = new FormData(e.currentTarget);
const obj = Object.fromEntries(data);

Object.fromEntries работает, если у каждого поля одно значение.

С файлами и множественными значениями:

const data = new FormData(e.currentTarget);
const obj: Record<string, FormDataEntryValue | FormDataEntryValue[]> = {};

for (const [key, value] of data.entries()) {
    if (key in obj) {
        const existing = obj[key];
        obj[key] = Array.isArray(existing) ? [...existing, value] : [existing, value];
    } else {
        obj[key] = value;
    }
}

Отправка с токеном

await fetch('/api/posts', {
    method: 'POST',
    headers: {
        Authorization: `Bearer ${token}`,
    },
    body: data,
});

Content-Type не трогай.

Проверка размера файла

function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();

    const data = new FormData(e.currentTarget);
    const file = data.get('cover') as File;

    if (file && file.size > 5 * 1024 * 1024) {
        alert('Файл больше 5 МБ');
        return;
    }

    // отправка
}

Итоги

  • FormData собирает поля по name
  • get, getAll, append, set, delete, has
  • Для файлов — type="file"
  • Не устанавливай Content-Type вручную
  • Object.fromEntries для преобразования в объект
  • Хорошо работает с fetch