Работа с FormData

В современном веб-разработке обработка форм и передача данных с клиента на сервер являются ключевыми задачами. React Router начиная с версии 6.4 предоставляет мощные инструменты для работы с формами через actions, позволяя интегрировать отправку данных и навигацию без использования сторонних библиотек.

Основы FormData

FormData — это стандартный объект JavaScript, который позволяет собирать пары ключ-значение из формы, включая файлы, и передавать их в HTTP-запросах. В контексте React Router FormData используется вместе с формами, определёнными компонентом <Form>.

Пример создания FormData вручную:

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

Каждое поле формы автоматически добавляется в объект FormData, включая файлы:

data.append('username', 'JohnDoe');
data.append('avatar', fileInput.files[0]);

Компонент <Form> в React Router

В React Router компонент <Form> заменяет привычный HTML <form> и автоматически интегрируется с маршрутизатором. Основные особенности:

  • Атрибут method указывает HTTP-метод (post, put, patch, delete).
  • Атрибут action определяет маршрут, на который будет отправлен запрос.
  • Внутри <Form> можно использовать обычные элементы <input>, <select>, <textarea>.

Пример использования:

import { Form } from "react-router-dom";

<Form method="post" action="/submit">
  <input name="username" type="text" />
  <input name="email" type="email" />
  <input name="avatar" type="file" />
  <button type="submit">Отправить</button>
</Form>

При отправке данных React Router автоматически собирает их в объект FormData и передаёт в action маршрута.

Actions и обработка FormData

Action — это функция, привязанная к маршруту, которая обрабатывает данные формы до того, как произойдёт навигация. Она получает объект Request, из которого можно извлечь данные формы:

export async function action({ request }) {
  const formData = await request.formData();
  const username = formData.get('username');
  const email = formData.get('email');
  const avatar = formData.get('avatar');

  // Дальнейшая обработка, например, отправка на сервер
  await fetch('/api/users', {
    method: 'POST',
    body: formData
  });

  return null;
}

Особенности работы с файлами: FormData корректно передаёт файлы без необходимости ручного создания multipart-запроса. Можно одновременно передавать текстовые поля и файлы в одном объекте FormData.

Обработка ошибок и валидация

React Router позволяет вернуть ошибки из action, используя объект json или redirect:

import { json } from "react-router-dom";

export async function action({ request }) {
  const formData = await request.formData();
  const username = formData.get('username');

  if (!username) {
    return json({ error: "Имя пользователя обязательно" }, { status: 400 });
  }

  // Сохранение данных
  return null;
}

На клиентской стороне можно использовать useActionData для получения ошибок и их отображения:

import { useActionData } from "react-router-dom";

const actionData = useActionData();

return (
  <div>
    {actionData?.error && <p>{actionData.error}</p>}
    <Form method="post">
      <input name="username" />
      <button type="submit">Отправить</button>
    </Form>
  </div>
);

Асинхронная обработка и загрузка файлов

Для больших файлов и длительных операций рекомендуется использовать fetch с объектом FormData внутри action. Можно использовать AbortController для отмены загрузки:

export async function action({ request }) {
  const formData = await request.formData();
  const controller = new AbortController();

  try {
    await fetch('/api/upload', {
      method: 'POST',
      body: formData,
      signal: controller.signal
    });
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('Загрузка отменена');
    } else {
      throw error;
    }
  }

  return null;
}

Динамическая генерация FormData

Иногда требуется формировать FormData программно, без использования <Form>. В этом случае можно создать объект и передать его через fetch или через custom action:

const formData = new FormData();
formData.append('name', 'Alice');
formData.append('profile', fileInput.files[0]);

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

Особенности интеграции с React Router

  • <Form> автоматически блокирует перезагрузку страницы при отправке.
  • Использование FormData с action позволяет сохранять SPA-поведение.
  • Любые файлы, добавленные через <input type="file">, автоматически добавляются в FormData.
  • Для динамических форм можно использовать FormData вне <Form>, но в этом случае необходимо самостоятельно вызывать fetch и обрабатывать навигацию.

Резюме ключевых моментов

  • FormData позволяет объединять текстовые поля и файлы в одном объекте.
  • <Form> React Router интегрируется с маршрутизатором и поддерживает отправку FormData.
  • Action маршрута получает Request и может асинхронно обрабатывать данные.
  • Ошибки валидации можно возвращать через json, а клиент получать через useActionData.
  • Для больших файлов и динамических форм можно формировать FormData вручную и использовать fetch.

Работа с FormData в React Router позволяет реализовать современную SPA-архитектуру, объединяя формы, асинхронные операции и обработку навигации без перезагрузки страницы, сохраняя чистый и удобный код.