Привет, бравый покоритель фронтенда! Если ты когда-либо ковырялся в React и думал: «Эх, как же устроить красивую и понятную обработку состояния?», то вот новсть: есть такой хук — useActionState, и он может стать твоим лучшим другом.
Но постой! Разве раньше не было чего-то похожего под названием useFormState? И почему одни разработчики импортируют это из react-dom
, а другие из react
? Если такие вопросы посещали твою светлую голову, то добро пожаловать: сейчас мы разберёмся, почему useFormState ушёл на пенсию и чем так хорош useActionState.
Коротко о былом
В экспериментальных (Canary) версиях React существовал хук useFormState, который жил в пакете react-dom
. Он помогал управлять состоянием формы и выполнял действия еще до того, как JavaScript успевал проснуться — всё звучит круто, да? Но в какой-то момент команда React решила, что функциональность этого хука гораздо шире, чем просто «форма». Так на его место пришёл useActionState, и обитает он уже не в react-dom
, а прямо в react
.
Иногда можно встретить статьи или примеры, где написано:
import { useFormState } from 'react-dom';
Но на дворе уже 19-я версия React (или ты можешь быть на другом экспериментальном канале), и useFormState больше не актуален. Теперь всё внимание на:
import { useActionState } from 'react';
Что это и как это работает
useActionState — это хук для тех, кто любит поддерживать порядок. Он позволяет управлять состоянием через «действия», то есть какие-то команды вроде «сделай это сейчас». При этом он чувствует себя отлично не только с формами, но и с любыми другими элементами, которые требуют обновления состояния при совершении какого-то действия.
Сигнатура проста:
const [currentState, actionFunction, isPending] = useActionState(fn, initialState, permalink?);
-
currentState — актуальное состояние (любого типа, который ты ему назначишь);
-
actionFunction — функция (или значение), которую можно прикреплять, например, к
formAction
или вызывать вручную; -
isPending — булевый флажок, который подскажет, находится ли действие в процессе выполнения (полезно для спиннеров, загрузок и прочих индикаторов).
А теперь подробнее о каждом параметре, который ты передаёшь в сам useActionState
:
-
fn — Главная функция, которую вызываем при действии (отправка формы, клик и т.д.).
-
Принимает предыдущее состояние (или начальное, если вызывается впервые)
-
Также получает все аргументы, которые обычно передаются при действии (например,
FormData
) -
Возвращает новое состояние, которое затем попадает в
currentState
-
-
initialState — Начальное значение для состояния. Может быть чем угодно (число, строка, объект).
-
Используется только один раз, до первого вызова
fn
-
После первого экшена итоговое состояние будет всегда возвращаться из
fn
-
-
permalink (необязательный) — Уникальный URL, куда перенаправляется форма, если JavaScript ещё не загрузился.
-
Полезно на динамических страницах (ленты товаров, блоги и т.д.)
-
На целевом URL должна рендериться та же форма (с тем же
fn
иpermalink
), чтобы React правильно передал состояние -
После гидратации этот параметр не используется, так как весь дальнейший рендеринг идёт на клиенте
-
Если тебе этот хук напоминает useState на стероидах — значит, ты на верном пути!
Почему не просто useState?
useState
— замечательный хук, и он с лёгкостью покрывает 90% базовых задач. Но бывают ситуации, когда в компоненте возникает 3–4 (или больше) состояний, связанных именно с результатом действия (например, сообщение об успехе/ошибке, флаг загрузки, дополнительная информация с сервера). Пример
const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [toastMessage, setToastMessage] = useState(''); // И это только начало...
Каждый раз заводить новый useState
для вспомогательного статуса/ошибки — утомительно, и код становится громоздким. Вот тут useActionState бьёт кулаком по столу и говорит: «Позволь, я покажу класс!». Он позволяет хранить все «результаты» в одном объекте (например, { success, error, message, }
) плюс имеет встроенный флаг isPending
. Проще говоря, если перед тобой стоит задача «отправить данные и получить ответ» — лучше взять useActionState, чем лепить кучу маленьких useState
.
Стоит учитывать, что когда говорят, что «useActionState может заменить множество
useState
», обычно имеют в виду именно вспомогательные состояния (статусы, ошибки, тексты уведомлений). Если же у тебя большая форма с десятью полями (имя, почта, пароль и т. д.), и ты хочешь управлять ими «всё в одном месте», то useActionState не подходит для всей логики: он не создан для многоэтапной работы над каждым полем. В таких случаях либо оставляют поля неконтролируемыми, либо используютuseState
/useReducer
для управления значениями самих инпутов. А useActionState дополнительно помогает показать, как «форма отправилась и вернулась с сервера» — то есть результат и статус запроса.
Отличия от useFormState
Вкратце: useFormState жил в react-dom
и подразумевал, что ты работаешь именно с формами. useActionState переехал в react
и теперь его сфера применения не ограничена тегом <form>
. Он работает с любыми действиями, будь то клик по кнопке, отправка формы, или вызов асинхронной функции, — и это чертовски удобно!
-
Подходит для применения не только внутри форм, но и в других интерфейсных элементах, таких как кнопки или обработчики событий.
-
Дружит с Server Components, позволяя обрабатывать состояние на сервере ещё до загрузки JS.
-
Название с «action» в имени как бы намекает, что дело не только в формах.
Базовый пример
Что может быть лучше счётчика? Только пекарня! BunBakery — место, где мы добавим на прилавок столько булочек, сколько захотите!
import { useActionState } from 'react'; const bakeBun = async (previousCount, formData) => { return previousCount + 1; }; export default function BunBakery() { // Начальное количество булочек на прилавке - 0 const [bunsCount, bakeBunAction, isBaking] = useActionState(bakeBun, 0); return ( <div> <p>Булочек на прилавке: {bunsCount}</p> <form> <button formAction={bakeBunAction} disabled={isBaking}> {isBaking ? 'Пеку пеку...' : 'Испечь булочку'} </button> </form> </div> ); }
Что здесь происходит?
-
Когда вы нажимаете кнопку, вызывается
bakeBunAction
, который внутри себя запускает функциюbakeBun()
. -
Пока булочка «печётся» (да, даже если это занимает доли секунды), флажок
isBaking
будет равенtrue
. -
Как только процесс завершится, количество булочек на прилавке (
bunsCount
) увеличится на 1.
Выглядит крайне лаконично. И это только вершина айсберга!
Пример «Загрузка файла»
Давай посмотрим, как useActionState
справляется с чем-то более серьёзным, чем простой счётчик. Например, создадим компонент для загрузки файлов —UploadForm
.
import { useActionState } from 'react'; // Функция обработки загрузки файла async function handleFileUpload(prevState, formData) { try { await new Promise(resolve => setTimeout(resolve, 2000)); return { success: true, message: 'Файл успешно загружен!' }; } catch (error) { return { success: false, message: 'Ошибка при загрузке файла.' }; } } function UploadForm() { const [uploadStatus, uploadFileAction, isUploading] = useActionState(handleFileUpload, null); return ( <form> <input type="file" name="file" /> <button formAction={uploadFileAction} disabled={isUploading}> {isUploading ? 'Загружаем...' : 'Загрузить файл'} </button> {uploadStatus && ( <p className={uploadStatus.success ? 'success' : 'error'}> {uploadStatus.message} </p> )} </form> ); } export default UploadForm;
Здесь есть несколько моментов:
-
uploadStatus
— наше текущее состояние, которое может содержать что угодно: сообщение об успехе/ошибке, дополнительные поля и т. д. -
uploadFileAction
— «экшен», который прикрепляется к кнопке через атрибутformAction
. Он запускает процесс загрузки файла. -
isUploading
— булевый флаг, показывающий, что файл загружается, и отключающий кнопку, чтобы избежать повторных кликов во время процесса.
Серверные функции и Server Components
Одна из важных «фишек» useActionState — это работа с Server Components и так называемыми «серверными действиями». Если ты используешь Next.js 13+ или другой фреймворк, поддерживающий React Server Components, то можешь вызывать серверную функцию напрямую:
import { useActionState } from 'react'; import { getDataFromServer } from './actions.server.js'; // "use server" export default function ServerDataFetcher() { // fetchData — «экшен», который запускает запрос на сервер const [fetchedData, fetchData, isPending] = useActionState(async (prevData) => { const result = await getDataFromServer(); return result; }, null); return ( <div> <button onClick={() => fetchData()} disabled={isPending}> {isPending ? 'Загружаем...' : 'Получить данные с сервера'} </button> {fetchedData && ( <p>Ответ сервера: {fetchedData}</p> )} </div> ); } // actions.server.js "use server"; export async function getDataFromServer() { // Имитируем запрос к серверу return 'Привет от сервера!'; }
Даже если JavaScript на клиенте отключен или не успел загрузиться, Server Components могут обработать твой запрос и передать готовый ответ. А когда клиент «проснётся» (гидратация завершится), React сопоставит обновлённое состояние с интерфейсом, так что пользователю всё равно всё будет работать «из коробки».
Best practices и мини-шпаргалка
-
Инициализируй состояние так, чтобы оно отражало реальный объект, с которым предстоит работать.
-
Обрабатывай ошибки возвращая из своей функции объект с
error
илиsuccess
, чтобы в интерфейсе легко было показать соответствующее сообщение. -
Пользуйся
isPending
. Не бойся показывать индикатор загрузки или дизейблить кнопку. Пользователи любят понимать, что что-то происходит. -
Не ограничивай себя формами. Если у тебя экшены не связаны с формой, смело вызывай
actionFunction()
внутриonClick
.
Итоги
useActionState — «прокачанный» вариант useState
, позволяющий удобно обрабатывать одно «действие» (будь то отправка формы, добавление в корзину или загрузка файла) и иметь встроенный флаг загрузки. Он идеально подходит для сценариев, где после нажатия на кнопку (или отправки формы) нужно дождаться ответа с сервера и показать, как всё прошло: успешно или с ошибкой.
-
Не для всего: если у тебя сложная форма с десятком полей и множеством отдельных действий, useActionState не заменит
useReducer
или несколькоuseState
. -
Для «быстрой магии»: когда тебе нужно всего лишь одно действие, результат и
isPending
, он сэкономит время и строки кода. -
Интеграция с Server Components: даёт возможность обрабатывать «серверные действия» до гидратации, что ускоряет UX и упрощает код.
Если ты искал способ упростить логику «запрос — результат — отображение» и при этом не хотел городить огород изuseState
, useActionState может стать тем самым «Ах, вот оно!».
ссылка на оригинал статьи https://habr.com/ru/articles/870216/
Добавить комментарий