Пошук уроків, статей та іншого контенту
Ознайомитеся з можливостями React 19: новим API, оновленим керуванням станом і сучасним підходом до форм.
React 19 розвиває підхід, у якому асинхронна логіка, оновлення стану та HTML-форми працюють разом. Значна частина типового коду тепер може бути описана без ручного керування прапорцями isLoading, обробниками onSubmit і викликами preventDefault().
Основні можливості:
Actions — функції для асинхронних змін даних;
useActionState — стан і результат асинхронної дії;
useFormStatus — стан форми в дочірньому компоненті;
useOptimistic — оптимістичні оновлення інтерфейсу;
підтримка ref як звичайного пропа;
можливість читати значення Promise або контексту за допомогою use.
У цьому уроці основну увагу приділимо формам і керуванню станом.
У попередніх версіях React форма зазвичай оброблялася вручну:
function ProfileForm() {
const [pending, setPending] = useState(false);
async function handleSubmit(event) {
event.preventDefault();
setPending(true);
try {
await saveProfile(new FormData(event.currentTarget));
} finally {
setPending(false);
}
}
return (
<form onSubmit={handleSubmit}>
{/* поля форми */}
<button disabled={pending}>
{pending ? "Збереження..." : "Зберегти"}
</button>
</form>
);
}React 19 дозволяє передати асинхронну функцію безпосередньо в атрибут action форми:
<form action={saveProfile}>
<input name="name" />
<button type="submit">Зберегти</button>
</form>Після надсилання React передасть функції об’єкт FormData. У ньому доступні значення полів, які мають атрибут name.
async function saveProfile(formData) {
const name = formData.get("name");
await fetch("/api/profile", {
method: "POST",
body: JSON.stringify({ name }),
headers: {
"Content-Type": "application/json",
},
});
}Actions зручні тим, що React може координувати їх виконання зі станом форми, очікуванням і переходами інтерфейсу.
useActionStateuseActionState поєднує асинхронну дію з її результатом і станом виконання.
const [state, formAction, isPending] = useActionState(
action,
initialState
);Хук повертає:
state — останній результат дії;
formAction — функцію, яку можна передати в action форми;
isPending — true, поки дія виконується.
Функція дії отримує два аргументи:
async function action(previousState, formData) {
// ...
}previousState — попереднє значення стану;
formData — дані поточної форми.
Це особливо корисно для повідомлень про помилки валідації або успішне збереження.
useActionStateНижче наведений повний приклад компонента. Він використовує:
useActionState для результату дії;
useFormStatus для стану кнопки;
useOptimistic для миттєвого відображення нового імені;
FormData для читання значень форми.
import {
useActionState,
useOptimistic,
} from "react";
import { useFormStatus } from "react-dom";
function fakeSaveProfile(name) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (name.length < 2) {
reject(new Error("Ім'я має містити щонайменше 2 символи"));
return;
}
resolve({ name });
}, 1000);
});
}
async function updateProfile(previousState, formData) {
const name = String(formData.get("name") || "").trim();
if (!name) {
return {
ok: false,
message: "Введіть ім'я",
};
}
try {
await fakeSaveProfile(name);
return {
ok: true,
message: "Профіль збережено",
name,
};
} catch (error) {
return {
ok: false,
message: error.message,
};
}
}
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? "Збереження..." : "Зберегти"}
</button>
);
}
function ProfileForm() {
const [state, formAction] = useActionState(updateProfile, {
ok: false,
message: "",
name: "Марія",
});
const [optimisticName, setOptimisticName] = useOptimistic(
state.name || "Марія"
);
async function action(formData) {
const nextName = String(formData.get("name") || "").trim();
if (nextName) {
// Показуємо нове ім'я ще до завершення запиту
setOptimisticName(nextName);
}
return formAction(formData);
}
return (
<section>
<p>
Поточне ім'я: <strong>{optimisticName}</strong>
</p>
<form action={action}>
<label>
Ім'я
<input
name="name"
defaultValue={state.name}
minLength={2}
/>
</label>
<SubmitButton />
{state.message && (
<p role="status">
{state.message}
</p>
)}
</form>
</section>
);
}
export default function App() {
return <ProfileForm />;
}Функція fakeSaveProfile імітує серверний запит. У реальному застосунку замість неї можна викликати fetch.
Важливо, що SubmitButton є дочірнім компонентом форми. Саме тому useFormStatus може отримати статус форми, до якої належить кнопка.
useFormStatususeFormStatus імпортується з react-dom:
import { useFormStatus } from "react-dom";Він повертає інформацію про найближчу батьківську форму:
const {
pending,
data,
method,
action,
} = useFormStatus();Найчастіше використовується pending:
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button disabled={pending}>
{pending ? "Надсилання..." : "Надіслати"}
</button>
);
}Хук не працює у компоненті, який сам безпосередньо рендерить форму:
function Form() {
const { pending } = useFormStatus();
return (
<form>
{/* pending не описує цю форму */}
</form>
);
}Кнопку або індикатор стану потрібно винести в дочірній компонент.
Action може повертати будь-яке серіалізоване значення, наприклад об’єкт із повідомленнями:
async function createUser(previousState, formData) {
const email = String(formData.get("email") || "");
if (!email.includes("@")) {
return {
success: false,
errors: {
email: "Введіть коректну електронну адресу",
},
};
}
await saveUser(email);
return {
success: true,
errors: {},
};
}Компонент читає результат через state:
{state.errors.email && (
<p role="alert">{state.errors.email}</p>
)}
{state.success && (
<p role="status">Користувача створено</p>
)}Такий підхід зберігає логіку дії в одному місці. Компонент форми відповідає переважно за відображення полів і результату.
useOptimisticuseOptimistic призначений для оптимістичних оновлень. Це означає, що інтерфейс змінюється одразу, не очікуючи завершення серверної операції.
const [optimisticState, addOptimistic] = useOptimistic(
actualState,
updateFunction
);Приклад:
const [optimisticLikes, addOptimisticLike] = useOptimistic(
likes,
(currentLikes, increment) => currentLikes + increment
);Після виклику:
addOptimisticLike(1);інтерфейс одразу покаже збільшену кількість вподобань. Коли Action завершиться, React синхронізує оптимістичне значення з фактичним станом.
Типовий сценарій:
користувач надсилає форму;
React показує очікуваний результат;
виконується асинхронна операція;
після успіху фактичний стан оновлюється;
після помилки інтерфейс повертається до фактичного стану.
Оптимістичне оновлення варто використовувати, коли операція зазвичай завершується успішно, а її результат можна передбачити заздалегідь.
ref як звичайний пропУ React 19 функціональний компонент може отримувати ref як звичайний проп. Для простих випадків більше не потрібно обов’язково використовувати forwardRef.
function SearchInput({ ref, placeholder }) {
return (
<input
ref={ref}
placeholder={placeholder}
type="search"
/>
);
}Використання:
import { useRef } from "react";
function Search() {
const inputRef = useRef(null);
function focusInput() {
inputRef.current?.focus();
}
return (
<>
<SearchInput
ref={inputRef}
placeholder="Пошук"
/>
<button type="button" onClick={focusInput}>
Перейти до пошуку
</button>
</>
);
}ref залишається спеціальним механізмом React, але в React 19 він може бути частиною списку параметрів функціонального компонента.
useReact 19 також надає API use. Він дозволяє читати значення Promise або контексту без окремого виклику useContext.
import { use } from "react";
function UserName({ userPromise }) {
const user = use(userPromise);
return <p>{user.name}</p>;
}Якщо Promise ще не завершений, компонент має бути всередині Suspense:
import { Suspense, use } from "react";
function UserName({ userPromise }) {
const user = use(userPromise);
return <p>{user.name}</p>;
}
function App({ userPromise }) {
return (
<Suspense fallback={<p>Завантаження...</p>}>
<UserName userPromise={userPromise} />
</Suspense>
);
}use можна викликати в умовах і циклах, на відміну від звичайних React-хуків. Однак Promise має бути стабільним і не створюватися заново під час кожного рендерингу компонента.
Для нових форм у React 19 корисно розділяти відповідальність:
форма описує поля та передає action;
Action виконує валідацію й асинхронне збереження;
useActionState зберігає результат;
useFormStatus показує стан очікування;
useOptimistic використовується, коли результат можна відобразити наперед.
Це зменшує кількість ручного коду:
не потрібно самостійно викликати event.preventDefault() для Action;
не потрібно окремо створювати pending для кожної форми;
не потрібно вручну передавати результат запиту через кілька рівнів компонентів;
повідомлення про помилки стають частиною стану дії.
Водночас action не скасовує серверну валідацію. Перевірку даних потрібно виконувати і на сервері, навіть якщо ті самі поля перевіряються в браузері.
useFormStatus у неправильному компонентіuseFormStatus читає стан батьківської форми. Якщо викликати його в тому самому компоненті, де форма створюється, він не отримає очікуваний статус.
Виносьте кнопку або індикатор завантаження в дочірній компонент.
nameFormData містить лише поля з атрибутом name:
<input name="email" />Без name значення не буде доступне через:
formData.get("email");event у ActionAction форми отримує FormData, а не об’єкт події:
async function action(formData) {
const value = formData.get("value");
}Для звичайного onSubmit об’єкт події все ще використовується, але це інший підхід.
Поки Action виконується, кнопку надсилання потрібно блокувати:
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button disabled={pending}>
Зберегти
</button>
);
}Інакше користувач може запустити кілька однакових операцій.
useOptimisticОптимістичний стан не замінює фактичний стан сервера. Якщо запит завершився помилкою, інтерфейс повинен повернутися до актуальних даних або показати помилку.
Такий код може спричинити повторне завантаження:
function App() {
const userPromise = fetchUser();
return <UserName userPromise={userPromise} />;
}Promise краще створювати поза компонентом, зберігати в стані або отримувати з іншого стабільного джерела.
React 19 спрощує роботу з асинхронними діями та HTML-формами.
action дозволяє передати асинхронну функцію безпосередньо формі.
useActionState повертає результат дії та її оновлену функцію.
useFormStatus допомагає відображати стан надсилання у дочірньому компоненті.
useOptimistic показує очікуваний результат до завершення запиту.
ref тепер можна передавати функціональному компоненту як звичайний проп.
use читає значення Promise або контексту та працює разом із Suspense.
Для надійних форм потрібні блокування повторного надсилання, обробка помилок і серверна валідація.