<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[React 19: useOptimistic и useActionState для пессимистичных форм с progressive enhancement]]></title><description><![CDATA[<p dir="auto"><img src="/assets/uploads/files/ce/9e/7e/1774082309813-generated_1774082284120.webp" alt="Обложка: React 19: useOptimistic с useActionState для пессимистичных обновлений форм с progressive enhancement и fallback состояниями" class=" img-fluid img-markdown" /></p>
<p dir="auto">React 19 притащил <strong>useOptimistic</strong> и <strong>useActionState</strong> - хуки, которые меняют правила игры с формами. Они позволяют делать пессимистичные обновления: UI показывает реальное состояние с фолбэками, пока сервер думает. Это решает боль задержек и спиннеров - юзер сразу видит, что происходит, даже если сетка легла.</p>
<p dir="auto">Progressive enhancement здесь в теме: формы работают без JS, а с React’ом добавляется магия optimistic’а. Зачем это? Меньше бойлерплейта, чище стейт, надежнее UX. Погнали разбирать на примерах.</p>
<h2>useActionState: стейт действий без лишнего кода</h2>
<p dir="auto"><strong>useActionState</strong> берет на себя управление стейтом асинхронных экшенов - pending, success, error в одном хуке. Вместо кучи useState для loading/error/success пишешь одну строку. Форма подключается через action={formAction}, и React сам обновляет UI по результату.</p>
<p dir="auto">Представь форму логина: юзер жмет submit, сервер проверяет креды. Без этого хука пришлось бы вручную сетить isLoading=true, потом обрабатывать ответ. С useActionState стейт всегда актуальный, кнопка дизаблится на pending. Это упрощает код в 2-3 раза, особенно с серверными экшенами.</p>
<p dir="auto">Ключевые фичи:</p>
<ul>
<li>Возвращает [state, action] - state это объект с pending/error/lastSuccess.</li>
<li>Поддерживает progressive enhancement: без JS форма шлет POST на сервер.</li>
<li>Интегрируется с useOptimistic для гибридных обновлений.</li>
</ul>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Свойство</th>
<th>Описание</th>
<th>Пример</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>pending</strong></td>
<td>true во время запроса</td>
<td>disabled={pending}</td>
</tr>
<tr>
<td><strong>error</strong></td>
<td>Строка с ошибкой сервера</td>
<td>{error &amp;&amp; <p>{error}</p>}</td>
</tr>
<tr>
<td><strong>lastSuccess</strong></td>
<td>Результат успеха</td>
<td>{lastSuccess?.message}</td>
</tr>
</tbody>
</table>
<p dir="auto">Пример простейшей формы:</p>
<pre><code class="language-jsx">const [state, formAction] = useActionState(loginAction, {error: null});
&lt;form action={formAction}&gt;
  &lt;input name="email" /&gt;
  &lt;button disabled={state.pending}&gt;Login&lt;/button&gt;
  {state.error &amp;&amp; &lt;p style={{color: 'red'}}&gt;{state.error}&lt;/p&gt;}
&lt;/form&gt;
</code></pre>
<p dir="auto"><em>Нюанс: initialState задай null или {}, чтобы избежать фликера.</em></p>
<h2>useOptimistic для пессимистичных обновлений</h2>
<p dir="auto"><strong>useOptimistic</strong> рендерит UI сразу по ожидаемому результату, откатывает при ошибке. Для пессимистичных форм это идеал: показывай фолбэк (спиннер, “отправляем…”), пока сервер не ответил. Комбинируй с useActionState - optimisticState берет реальный стейт и мутирует его.</p>
<p dir="auto">Возьми список тасок: юзер добавляет новую, UI сразу показывает “sending…”. Сервер ответил - обновляем, ошибка - роллбэк. Без этого хука приходилось дублировать стейт optimistic/real. Теперь один хук - два стейта: реальный и UI-версия.</p>
<p dir="auto">Как это работает:</p>
<ul>
<li><code>const [optimisticTasks, addOptimistic] = useOptimistic(tasks, (state, newTask) =&gt; [...state, {text: newTask, sending: true}]);</code></li>
<li>В formAction: addOptimistic(formData.get(‘task’)); await sendToServer();</li>
<li>При ошибке стейт откатывается автоматически.</li>
</ul>
<p dir="auto">Плюсы пессимистичного подхода:</p>
<ul>
<li><strong>Fallback’и</strong>: всегда показывай pending-state, даже оффлайн.</li>
<li><strong>Progressive</strong>: форма работает без React.</li>
<li><strong>Rollback</strong>: ошибка? optimisticState синхронится с реальным.</li>
</ul>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Optimistic vs Pessimistic</th>
<th>Когда юзать</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Optimistic</strong></td>
<td>Лайки, комментарии (предполагаем успех)</td>
</tr>
<tr>
<td><strong>Pessimistic</strong></td>
<td>Формы с критичными данными (логин, оплата)</td>
</tr>
</tbody>
</table>
<p dir="auto">Код для тасок:</p>
<pre><code class="language-jsx">function Tasks({tasks, sendTasks}) {
  const [optimisticTasks, addOptimisticTask] = useOptimistic(tasks, (state, newTask) =&gt; [...state, {text: newTask, sending: true}]);
  async function formAction(formData) {
    addOptimisticTask(formData.get('task'));
    await sendTasks(formData);
  }
  return (
    &lt;&gt;
      {optimisticTasks.map(t =&gt; &lt;div&gt;{t.text} {t.sending &amp;&amp; '(отправляем...)'}&lt;/div&gt;)}
      &lt;form action={formAction}&gt;&lt;input name="task" /&gt;&lt;button&gt;Add&lt;/button&gt;&lt;/form&gt;
    &lt;/&gt;
  );
}
</code></pre>
<h2>Комбо: useActionState + useOptimistic с progressive enhancement</h2>
<p dir="auto">Соединяем хуки: useActionState управляет form-state, useOptimistic - UI-обновлениями. Форма шлет серверный экшен, optimistic добавляет фолбэк сразу. Progressive enhancement на месте: без JS - обычный POST, с JS - мгновенный фидбек.</p>
<p dir="auto">Пример - редактирование профиля. Юзер меняет email, форма показывает “saving…” optimistic’но, сервер валидирует. Ошибка? Роллбэк + error из useActionState. Это убивает спиннеры и лоадеры - UI живой всегда.</p>
<p dir="auto">Шаги реализации:</p>
<ol>
<li>Создай серверный action с async/await.</li>
<li>Подключи useActionState для стейта формы.</li>
<li>Добавь useOptimistic для списка/формы.</li>
<li>В action: optimistic update -&gt; await server -&gt; sync.</li>
</ol>
<pre><code class="language-jsx">const [state, formAction] = useActionState(updateProfile, {error: null});
const [optimisticProfile, updateOptimistic] = useOptimistic(profile, (st, newEmail) =&gt; ({...st, email: newEmail, saving: true}));
// В action: updateOptimistic(formData.get('email')); return await api.update();
</code></pre>
<p dir="auto">Сравнение подходов:</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Подход</th>
<th>Boilerplate</th>
<th>UX</th>
<th>Progressive</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>useState + useEffect</strong></td>
<td>Высокий</td>
<td>Спиннеры</td>
<td>Нет</td>
</tr>
<tr>
<td><strong>useActionState solo</strong></td>
<td>Низкий</td>
<td>Хороший</td>
<td>Да</td>
</tr>
<tr>
<td><strong>+ useOptimistic</strong></td>
<td>Минимальный</td>
<td><img src="https://forum.exlends.ru/assets/plugins/nodebb-plugin-emoji/emoji/android/1f525.png?v=a1e94250dac" class="not-responsive emoji emoji-android emoji--fire" style="height:23px;width:auto;vertical-align:middle" title="🔥" alt="🔥" /> Идеал</td>
<td>Да</td>
</tr>
</tbody>
</table>
<p dir="auto"><em>Важно: uncontrolled inputs - не синхронизируй value с state, пусть FormData рулит.</em></p>
<h2>Пессимистичные фолбэки - когда оптимизм подведет</h2>
<p dir="auto">Фолбэки в пессимистичном стиле - это pending-states в optimisticState. Показывай “loading”, “error”, не жди сервера. С useFormStatus внутри формы - кнопка сама знает pending. Комбо с роутерами типа Next.js - серверные actions на стероидах.</p>
<p dir="auto">Думай о слабом интернете: optimistic рендерит фолбэк, useActionState ловит error. Нет нужды в глобальном loading - локальные хуки все тянут.</p>
<p dir="auto">Фичи для продакшена:</p>
<ul>
<li>Rollback в catch: addOptimistic((prev) =&gt; prev - 1) для лайков.</li>
<li>Hydration: SSR + client optimistic синхронятся.</li>
<li>Error boundaries не нужны - стейт сам хэндлит.</li>
</ul>
<p dir="auto">Код фолбэка:</p>
<pre><code class="language-jsx">function LikeButton({likes}) {
  const [optLikes, addOpt] = useOptimistic(likes, (prev) =&gt; prev + 1);
  async function handle() {
    addOpt();
    try { await fetch('/like'); } catch { addOpt((p) =&gt; p - 1); }
  }
  return &lt;button onClick={handle}&gt;❤️ {optLikes}&lt;/button&gt;;
}
</code></pre>
<h2>Гибридные паттерны выходят за рамки форм</h2>
<p dir="auto">useOptimistic + useActionState работают не только с формами - лайки, чат, CRUD. Остается копать интеграцию с TanStack Query или SWR для кэша. Подумай о custom hooks для типичных сценариев: optimisticDelete, optimisticPatch. В React 19 это база для быстрых apps - меньше костылей, больше нативности.</p>
]]></description><link>https://forum.exlends.ru/topic/1798/react-19-useoptimistic-i-useactionstate-dlya-pessimistichnyh-form-s-progressive-enhancement</link><generator>RSS for Node</generator><lastBuildDate>Sat, 17 Oct 2026 03:53:13 GMT</lastBuildDate><atom:link href="https://forum.exlends.ru/topic/1798.rss" rel="self" type="application/rss+xml"/><pubDate>Sat, 21 Mar 2026 08:38:30 GMT</pubDate><ttl>60</ttl></channel></rss>