ГлавнаяБлогuseFormStatus: как читать состояние формы из любого компонента
Алгоритмы

useFormStatus: как читать состояние формы из любого компонента

useFormStatus в React 19: как получить pending из дочерних компонентов без prop drilling. Примеры, подводные камни и практические советы. Читайте и применяйте!

Al
Редакция Algolitalgolit.ru
7 мин чтения12 августа 2026 г.

useFormStatus: как читать состояние формы из любого компонента

Если вы когда-нибудь передавали проп isPending через три уровня компонентов, чтобы просто задизейблить кнопку, — эта статья для вас. В React 19 появился хук useFormStatus, который решает эту проблему элегантно. Но у него есть одно важное правило, нарушив которое, вы потратите десять минут на поиск ошибки. Давайте разберёмся, как работает этот хук и где его действительно стоит применять.

Проблема: prop drilling при передаче состояния отправки

Представьте форму заказа, разбитую на несколько компонентов: поля оплаты в одном файле, кнопка — в другом. Оба должны знать, идёт ли отправка прямо сейчас. Без useFormStatus вам придётся прокидывать isPending от родителя к каждому ребёнку.

import { useActionState } from "react";

function PaymentFields({ disabled }) {
  return (
    <fieldset disabled={disabled}>
      <input name="cardNumber" placeholder="Card number" />
      <input name="cardExpiry" placeholder="MM/YY" />
    </fieldset>
  );
}

function PlaceOrderButton({ pending }) {
  return (
    <button type="submit" disabled={pending}>
      {pending ? "Placing order..." : "Place order"}
    </button>
  );
}

function OrderForm() {
  const [state, formAction, isPending] = useActionState(placeOrder, { message: "" });
  return (
    <form action={formAction}>
      <PaymentFields disabled={isPending} />
      <PlaceOrderButton pending={isPending} />
    </form>
  );
}

Это работает, но каждый новый компонент (секция доставки, купон, сайдбар) требует ещё один проп. useFormStatus создан, чтобы избавиться от этой рутины.

Ошибка: вызов хука в компоненте, который рендерит форму

Моя первая попытка — вызвать useFormStatus прямо в OrderForm, как замену isPending. Логично же?

import { useFormStatus } from "react-dom";

function OrderForm() {
  const { pending } = useFormStatus(); // всегда false
  return (
    <form action={placeOrder}>
      <PaymentFields disabled={pending} />
      <PlaceOrderButton pending={pending} />
    </form>
  );
}

Нажимаю «Place order» — ничего не происходит. Кнопка активна, сеть показывает запрос. Ни предупреждений, ни ошибок. Я потратил несколько минут, проверяя асинхронность placeOrder, прежде чем понял: дело в самом хуке.

Правило: компонент, вызывающий useFormStatus, должен быть потомком формы, а не тем, кто её рендерит. Когда OrderForm вызывает хук, он ещё не создал <form> — тот находится в процессе рендеринга. Для хука нет родительской формы, поэтому он возвращает состояние по умолчанию: pending: false.

Решение: вынесите хук в дочерние компоненты

Исправление простое: переместите вызов useFormStatus в те компоненты, которые находятся внутри <form>.

import { useFormStatus } from "react-dom";

function PaymentFields() {
  const { pending } = useFormStatus();
  return (
    <fieldset disabled={pending}>
      <input name="cardNumber" placeholder="Card number" />
      <input name="cardExpiry" placeholder="MM/YY" />
    </fieldset>
  );
}

function PlaceOrderButton() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? "Placing order..." : "Place order"}
    </button>
  );
}

function OrderForm() {
  return (
    <form action={placeOrder}>
      <PaymentFields />
      <PlaceOrderButton />
    </form>
  );
}

Теперь pending обновляется корректно. Никаких пропов — каждый ребёнок сам спрашивает у родительской формы её статус.

Важная деталь: хук из react-dom, а не из react

Не перепутайте импорт: useFormStatus находится в react-dom, а не в react. Если вы, как и я, привыкли к useActionState из react, легко ошибиться. Ошибка не вызовет предупреждение, просто всё перестанет работать.

Что ещё возвращает useFormStatus: data, method, action

Когда pending заработал, я думал, что это всё. Но хук возвращает объект с четырьмя свойствами:

const { pending, data, method, action } = useFormStatus();
  • data — объект FormData текущей отправки, доступен только пока идёт запрос. Я использовал его, чтобы сделать текст кнопки конкретнее:
function PlaceOrderButton() {
  const { pending, data } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? `Placing order for ${data.get("email")}...` : "Place order"}
    </button>
  );
}

Обратите внимание: data равен null, когда отправка не идёт. Поэтому data.get("email") безопасен только внутри ветки pending. Если вынести его наружу — компонент упадёт при первом рендере.

  • method — метод отправки. Для функциональных действий всегда 'post', независимо от атрибута method. 'get' появится только если action — обычная строка URL или его нет вовсе.
  • action — ссылка на функцию, переданную в action формы. Если функции нет (или формы нет) — null. Проверка на null не различает эти два случая.

Неожиданный бонус: переиспользование компонентов

Мой компонент PlaceOrderButton случайно оказался переиспользован на другой форме — «Сохранить черновик заказа». Я ожидал, что понадобится контекст или ID, чтобы различить формы. Ничего не нужно! useFormStatus сам находит ближайшую родительскую форму в каждом случае. Тот же компонент корректно читал статус на обеих формах без единой строчки дополнительного кода.

Подводный камень: атрибут form="..."

А вот что меня удивило: в админке кнопка была вынесена за пределы формы с помощью HTML-атрибута form="orderForm". Это валидный HTML, и отправка работает. Но useFormStatus внутри такой кнопки всегда возвращает pending: false, даже когда форма отправляется. Хук работает с деревом компонентов, а не с DOM-атрибутами. Для него эта кнопка — не потомок формы.

Когда не стоит использовать useFormStatus

Для простой формы из двух полей и одной кнопки в одном компоненте useActionState с его isPending — проще и не требует второго хука. useFormStatus оправдан, когда части формы реально разнесены по компонентам. И помните: хук сообщает о статусе уже начавшейся отправки, а не валидирует её. Проверки — удел action-функции.

Где живут useActionState, useOptimistic и useFormStatus

Теперь, когда мы прошли три части, картина ясна:

  • useActionState — владеет результатом действия и реагирует на него из места вызова.
  • useOptimistic — показывает оптимистичный UI до подтверждения сервера.
  • useFormStatus — отвечает на вопрос «форма отправляется?» из любого дочернего компонента без прокидывания пропсов.

Ключевое различие: isPending из useActionState следует за конкретным действием, а pending из useFormStatus — за формой, в которой находится компонент. Поэтому один и тот же PlaceOrderButton работал на двух разных формах с разными действиями.

Эти хуки не конфликтуют. В моей итоговой форме заказа я использовал оба: useActionState в родителе для результата, useFormStatus в каждом дочернем разделе для статуса.

Практический вывод

Если у вас есть форма, где кнопка отправки находится на три уровня глубже компонента, управляющего отправкой, — проверьте, где именно вы вызвали useFormStatus. Скорее всего, вы сделали ту же ошибку, что и я, и потеряли десять минут. А теперь примените знание: вынесите хук в дочерние компоненты, и состояние будет работать без магии.

#useFormStatus#React 19#формы#хуки#состояние отправки
Al
Редакция Algolit

Пишем про алгоритмы, подготовку к собеседованиям и карьеру в IT — так, чтобы было понятно и полезно.

Хочешь закрепить знания на практике?

Решай задачи на Algolit — интерактивная платформа для обучения

Начать бесплатно →