Новые возможности React 19

21 июля 2026
Автор

Антон Ларичев

React 19 — первый мажорный релиз после React 18, который вышел в 2022 году. Он привнёс концепцию Actions как стандартный способ работы с асинхронными операциями, стабилизировал Server Components и Server Actions, добавил несколько новых хуков и упростил ряд паттернов, которые раньше требовали обходных решений.

В этой статье разберём каждое нововведение с практическими примерами.

Концепция Actions

Actions — центральная идея React 19. В предыдущих версиях не существовало стандартного способа обработки асинхронных операций (отправка форм, мутации данных) с управлением pending-состоянием и ошибками. Каждый разработчик решал это по-своему.

React 19 вводит соглашение: функция, которая принимает переход (transition) и выполняет асинхронную операцию, называется Action.

async function updateUser(formData: FormData) {
  'use server'; // только для Server Actions
  const name = formData.get('name') as string;
  await db.user.update({ data: { name } });
}

Actions интегрированы с элементом <form> через проп action, а также с новыми хуками useActionState и useFormStatus.

Курс по теме

Курс React и Redux Toolkit с нуля

40 000+ студентов · рейтинг 4.8 · гарантия возврата 30 дней

Хук useActionState

useActionState — новый хук для управления состоянием Action. Он заменяет ручное отслеживание isPending, error и результата операции.

import { useActionState } from 'react';

type State = {
  error: string | null;
  success: boolean;
};

async function submitForm(
  prevState: State,
  formData: FormData
): Promise<State> {
  const name = formData.get('name') as string;

  if (!name || name.length < 2) {
    return { error: 'Имя должно содержать минимум 2 символа', success: false };
  }

  await updateUserName(name);
  return { error: null, success: true };
}

function UserForm() {
  const [state, formAction, isPending] = useActionState(submitForm, {
    error: null,
    success: false,
  });

  return (
    <form action={formAction}>
      <input name="name" type="text" disabled={isPending} />
      {state.error && <p style={{ color: 'red' }}>{state.error}</p>}
      {state.success && <p style={{ color: 'green' }}>Сохранено</p>}
      <button type="submit" disabled={isPending}>
        {isPending ? 'Сохранение...' : 'Сохранить'}
      </button>
    </form>
  );
}

Хук принимает функцию-Action и начальное состояние, возвращает кортеж из трёх элементов:

  • state — текущее состояние (результат последнего вызова Action или начальное значение)
  • formAction — функция, которую передаём в проп action формы
  • isPending — флаг выполнения асинхронной операции

Функция Action получает prevState первым аргументом и formData вторым — это стандартный сигнатурный контракт для всех Actions.

Хук useFormStatus

useFormStatus позволяет дочернему компоненту внутри формы получить статус родительской формы без пробрасывания пропсов.

import { useFormStatus } from 'react-dom';

function SubmitButton() {
  const { pending } = useFormStatus();

  return (
    <button type="submit" disabled={pending}>
      {pending ? 'Отправка...' : 'Отправить'}
    </button>
  );
}

function ContactForm() {
  const [state, formAction] = useActionState(sendMessage, null);

  return (
    <form action={formAction}>
      <textarea name="message" />
      <SubmitButton />
    </form>
  );
}

Важный момент: useFormStatus работает только внутри компонента, который является потомком элемента <form>. Хук читает контекст формы через React-контекст и недоступен в самом компоненте формы.

Помимо pending, хук возвращает data, method и action — полные данные о форме и выполняемом запросе.

Хук useOptimistic

useOptimistic решает задачу оптимистичных обновлений UI — когда интерфейс реагирует мгновенно, не дожидаясь ответа сервера.

import { useOptimistic, useActionState } from 'react';

type Todo = {
  id: number;
  text: string;
  completed: boolean;
};

type AddTodoAction = {
  type: 'add';
  todo: Todo;
};

function TodoList({ initialTodos }: { initialTodos: Todo[] }) {
  const [optimisticTodos, addOptimisticTodo] = useOptimistic<Todo[], AddTodoAction>(
    initialTodos,
    (state, action) => {
      if (action.type === 'add') {
        return [...state, action.todo];
      }
      return state;
    }
  );

  const [, formAction] = useActionState(async (_: null, formData: FormData) => {
    const text = formData.get('text') as string;
    const tempTodo = { id: Date.now(), text, completed: false };

    addOptimisticTodo({ type: 'add', todo: tempTodo });

    const savedTodo = await createTodo(text);
    return null;
  }, null);

  return (
    <>
      <ul>
        {optimisticTodos.map((todo) => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
      <form action={formAction}>
        <input name="text" type="text" />
        <button type="submit">Добавить</button>
      </form>
    </>
  );
}

Хук принимает текущее состояние и функцию-редьюсер, которая описывает как применять оптимистичное обновление. Пока выполняется реальный запрос, компонент отображает оптимистичное значение. После завершения запроса React автоматически заменяет его актуальными данными.

API use()

use() — новый примитив, который позволяет читать значение Promise или Context внутри компонента, в том числе условно.

Чтение Promise

import { use, Suspense } from 'react';

async function fetchUser(id: number): Promise<User> {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
}

function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
  const user = use(userPromise);

  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

function Page({ userId }: { userId: number }) {
  const userPromise = fetchUser(userId);

  return (
    <Suspense fallback={<p>Загрузка...</p>}>
      <UserProfile userPromise={userPromise} />
    </Suspense>
  );
}

В отличие от await, use() можно вызывать условно и в циклах. Если Promise ещё не выполнен, компонент приостанавливается и показывается ближайший Suspense fallback.

Чтение Context

import { use, createContext } from 'react';

const ThemeContext = createContext<'light' | 'dark'>('light');

function Button({ children }: { children: React.ReactNode }) {
  const isAdmin = checkAdmin();

  // use() можно вызывать условно, в отличие от useContext
  if (!isAdmin) {
    return null;
  }

  const theme = use(ThemeContext);

  return (
    <button className={`btn btn--${theme}`}>
      {children}
    </button>
  );
}

Для чтения Context use() функционально эквивалентен useContext(), но снимает ограничение «хук только на верхнем уровне».

ref как проп (без forwardRef)

Одно из самых долгожданных упрощений — ref теперь является обычным пропом функциональных компонентов. forwardRef больше не нужен.

// React 18 — требовался forwardRef
const Input = forwardRef<HTMLInputElement, InputProps>(
  ({ label, ...props }, ref) => (
    <label>
      {label}
      <input ref={ref} {...props} />
    </label>
  )
);

// React 19 — ref передаётся как обычный проп
function Input({ label, ref, ...props }: InputProps & { ref?: React.Ref<HTMLInputElement> }) {
  return (
    <label>
      {label}
      <input ref={ref} {...props} />
    </label>
  );
}

// Использование одинаковое
function Form() {
  const inputRef = useRef<HTMLInputElement>(null);

  return <Input label="Имя" ref={inputRef} />;
}

forwardRef сохраняется в React 19 для обратной совместимости, но помечен как устаревший и будет удалён в будущих версиях.

Context как провайдер

Eщё одно упрощение синтаксиса: теперь можно использовать сам объект Context в качестве провайдера, без .Provider.

const ThemeContext = createContext<'light' | 'dark'>('light');

// React 18
function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Page />
    </ThemeContext.Provider>
  );
}

// React 19 — Provider не нужен
function App() {
  return (
    <ThemeContext value="dark">
      <Page />
    </ThemeContext>
  );
}

Старый синтаксис <ThemeContext.Provider> продолжает работать, но будет устаревшим.

Нативная поддержка метаданных документа

React 19 позволяет использовать теги <title>, <meta> и <link> прямо в JSX компонентов. React автоматически поднимает их в <head> документа.

function ProductPage({ product }: { product: Product }) {
  return (
    <>
      <title>{product.name} — Магазин</title>
      <meta name="description" content={product.description} />
      <link rel="canonical" href={`/products/${product.slug}`} />

      <main>
        <h1>{product.name}</h1>
        <p>{product.description}</p>
      </main>
    </>
  );
}

Это работает и на клиенте, и при серверном рендеринге. При Client-Side Navigation теги корректно обновляются — удаляются теги предыдущей страницы и добавляются теги новой.

API предзагрузки ресурсов

React 19 добавляет набор функций для управления загрузкой ресурсов. Они помогают оптимизировать производительность, давая браузеру ранние подсказки.

import { prefetchDNS, preconnect, preload, preinit } from 'react-dom';

function App() {
  // DNS-резолвинг для внешнего домена
  prefetchDNS('https://fonts.googleapis.com');

  // Установка соединения заранее
  preconnect('https://api.example.com');

  // Загрузка шрифта
  preload('https://fonts.googleapis.com/css2?family=Inter', {
    as: 'style',
  });

  // Загрузка и выполнение скрипта
  preinit('https://cdn.example.com/analytics.js', {
    as: 'script',
  });

  return <Router />;
}

Эти функции можно вызывать в любом месте рендера — React дедуплицирует вызовы и генерирует соответствующие HTML-подсказки (в случае SSR) или вставляет теги в <head> (на клиенте).

Улучшения Server Components и Server Actions

Server Components и Server Actions, которые появились в экспериментальном режиме в React 18, теперь являются стабильной частью React 19.

Server Actions через директиву use server

// actions.ts
'use server';

import { revalidatePath } from 'next/cache';

export async function deletePost(postId: number): Promise<void> {
  await db.post.delete({ where: { id: postId } });
  revalidatePath('/posts');
}

Передача Server Actions в Client Components

// PostList.tsx (Server Component)
import { deletePost } from './actions';
import { DeleteButton } from './DeleteButton';

export async function PostList() {
  const posts = await db.post.findMany();

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          {post.title}
          <DeleteButton postId={post.id} onDelete={deletePost} />
        </li>
      ))}
    </ul>
  );
}

// DeleteButton.tsx (Client Component)
'use client';

import { useActionState } from 'react';

type Props = {
  postId: number;
  onDelete: (postId: number) => Promise<void>;
};

export function DeleteButton({ postId, onDelete }: Props) {
  const [, action, isPending] = useActionState(
    async () => {
      await onDelete(postId);
    },
    null
  );

  return (
    <button onClick={() => action()} disabled={isPending}>
      {isPending ? 'Удаление...' : 'Удалить'}
    </button>
  );
}

Улучшенные сообщения об ошибках гидратации

Ошибки гидратации в React 18 выдавали малоинформативные сообщения. React 19 показывает конкретный фрагмент HTML, в котором возникло расхождение между серверным и клиентским деревом.

Пример нового формата ошибки:

Hydration failed because the server rendered HTML didn't match the client.

Server:   <div class="dark">
Client:   <div class="light">

  ...
    <ThemeProvider>
  +   <div class="light">
  -   <div class="dark">

Это значительно ускоряет отладку SSR-приложений.

Очистка ref

React 19 поддерживает возврат функции очистки из ref-коллбэка. Это эквивалент деструктора для ref.

function VideoPlayer({ src }: { src: string }) {
  return (
    <video
      ref={(node) => {
        if (!node) return;

        const player = initVideoPlayer(node, src);

        // Функция очистки — вызывается при размонтировании
        return () => {
          player.destroy();
        };
      }}
      controls
    />
  );
}

Раньше нужно было проверять node === null внутри ref-коллбэка вручную. Теперь React вызывает возвращённую функцию при размонтировании, а сам коллбэк — только при монтировании с реальным DOM-узлом.

Изменения в useDeferredValue

useDeferredValue получил необязательный параметр initialValue, который используется при первом рендере.

function SearchResults({ query }: { query: string }) {
  // При первом рендере используется пустая строка,
  // чтобы не блокировать начальный рендер
  const deferredQuery = useDeferredValue(query, '');

  return <ResultsList query={deferredQuery} />;
}

Это особенно полезно в Server Components: начальный рендер использует initialValue, а после гидратации значение обновляется до актуального query.

Миграция с React 18

React 19 содержит ряд breaking changes:

  • Удалены устаревшие API: ReactDOM.render, ReactDOM.hydrate, ReactDOMServer.renderToString (без streaming)
  • Удалён defaultProps для функциональных компонентов — используйте деструктуризацию со значениями по умолчанию
  • Изменено поведение Context.displayName
  • Строгий режим двойного вызова эффектов теперь применяется ещё агрессивнее

Для большинства приложений миграция сводится к:

npx react-codemod update-react-imports

После чего — замена устаревших API и проверка, что компоненты корректно работают при двойном вызове эффектов в строгом режиме.

Итог

React 19 систематизирует паттерны работы с асинхронными операциями через концепцию Actions и новые хуки useActionState, useFormStatus, useOptimistic. Новый примитив use() упрощает работу с Promise и Context. Синтаксические улучшения — ref как проп, Context без .Provider — убирают шаблонный код. Нативная поддержка метаданных документа и API предзагрузки закрывают задачи, которые раньше решались сторонними библиотеками или фреймворками.

Стабилизация Server Components и Server Actions делает React 19 основой для полноценных full-stack приложений без необходимости в дополнительных инструментах для управления серверной логикой.

Для глубокого изучения React и применения всех этих возможностей на практике — курс по React на PurpleSchool.

Стрелочка влевоУправление состоянием в React через ContextКак предотвратить лишние ре-рендеры в React: полное руководствоСтрелочка вправо

Постройте личный план изучения React до уровня Middle — бесплатно!

React — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по React

Безопасность в React: защита от XSS, CSRF и утечек данныхUncontrolled Components: когда DOM управляет даннымиRender Props: гибкое управление рендерингом в ReactРефакторинг React-кода: техники и лучшие практикиПрофилирование React: как найти и устранить узкие местаЧастичное применение: как создавать компоненты без лишнего кодаИменование компонентов в React: соглашения и лучшие практикиЛенивая загрузка: как ускорить React-приложение в разыHOC в React: мастерство композиции компонентовuseMemo: как спасти производительность от тяжелых вычисленийError Boundaries: создаем надежные React-приложенияКонтролируемые компоненты в React: полный контроль над формамиCompound Components в React: создаем гибкие компоненты с мощным APIДокументирование компонентов в React: Storybook, JSDoc и READMEКомпозиция компонентов в React: строим гибкие интерфейсыКомментирование кода в React: когда и как писать комментарииCode Splitting в React: как уменьшить бандл и ускорить загрузку приложенияАсинхронные компоненты в React: новый стандарт работы с даннымиДоступность (a11y) в React: ARIA, семантика и клавиатурная навигация
Zustand — управление состоянием в ReactZod - валидация с TypeScriptYup - валидация схемXState - конечные автоматыТемизация в ReactТестирование хуковTailwind CSS с ReactSWR - библиотека для запросовStyled Components — стилизация через JSStorybook - документация компонентовSnapshots тестированиеRTK Query - работа с APIRedux Toolkit - современный ReduxRecoil — библиотека управления состоянием от FacebookВиртуализация списков с react-window: как отображать тысячи элементов без лаговReact Toastify - уведомления в ReactReact Testing LibraryСоздание таблиц в React гайд по react-tableReact Spring - анимацииРабота с формами и селектами в ReactReact Query (TanStack Query) - работа с серверомПлагины в React что это и как их использоватьReact PDF - работа с PDF файламиОбзор популярных библиотек для ReactReact Icons - библиотека иконок для ReactReact Hook Form — валидация форм в ReactReact Dropzone — загрузка файловПодключение Bootstrap к React-приложениюReact Beautiful DnD - перетаскивание элементовАнимация при монтировании компонентов в ReactМокирование APIMobX — реактивное управление состоянием в ReactЗагрузка и индикаторыМикрофронтенды с React (micro-frontends)Анимация списков в ReactJotai - атомарное состояниеБесконечная прокруткаFramer Motion - библиотека анимацийEmotion — библиотека CSS-in-JSДинамические стили в ReactE2E тестирование с CypressCSSTransition - переходыCSS-in-JS — плюсы и минусыКонтекст vs Redux — когда что использоватьИспользование Chart.js в ReactAxios с ReactТестирование асинхронных компонентовОбработка ошибок API
useState в React что это и как использоватьuseTransition - плавные переходы между состояниямиuseSyncExternalStore — работа с внешними сторамиuseRef в React — создание ссылок на DOM и значенияuseOptimistic — оптимистичные обновления UIuseLayoutEffect в React — эффект до отрисовкиuseInsertionEffect — внедрение стилей до мутаций DOMuseImperativeHandle в React — настройка ref дочернего компонентаuseId — генерация уникальных идентификаторовuseFormStatus - отслеживание статуса отправки формыuseDeferredValue — отложенное обновление состоянияuseDebugValue — отладка кастомных хуковuseCallback в React — мемоизация функцийuseReducer — альтернатива useState для сложной логикиuseMemo в React: как и когда оптимизировать тяжелые вычисленияХук use() в React 19useEffect в React что это и как использоватьuseContext — работа с контекстом в ReactuseCallback в React — мемоизация функций и оптимизация ре-рендеровuseActionState в React 19Оптимизация рендеринга в React: от теории к глубокой практикеЧто такое useRef и как его применять в ReactКак и зачем использовать React HooksУправление состоянием в React через ContextНовые возможности React 19Как предотвратить лишние ре-рендеры в React: полное руководствоuseMemo vs useCallback: подробное руководство по мемоизации в ReactПравила хуков — правила использованияuseEffect vs useLayoutEffect: в чём разница и какой хук выбрать?Кастомные хуки в React — создание собственных хуковuseState продвинутое использование в React
Transition API — плавные обновления интерфейса в ReactReact Suspense — приостановка рендераStrictMode в React — как находить ошибки на этапе разработкиСерверные компоненты React (RSC) — подробный разбор и практикаКак работает рендеринг в ReactЧто такое props в React и как их правильно использоватьКак работает JSX связка React и HTMLЧто такое React.js и как его использоватьКак использовать элементы в ReactКак использовать React DOM в проектеЧто такое компоненты в React и как их применятьРабота с children в ReactПорталы в React: рендер компонентов вне иерархии DOMFragment в React: группировка элементов без лишних узлов DOMCSS Modules в ReactConcurrent Mode — конкурентный режим в React
Открыть базу знаний

Лучшие курсы по теме

изображение курса

React и Redux Toolkit

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий