Co nowego w React 19? Kompletny przewodnik po nowych hookach na rozmowie rekrutacyjnej w 2026 roku

Sławomir Plamowski 13 min czytania
frontend hooks interview-questions react react-19 rekrutacja useActionState

Wyobraź sobie rozmowę rekrutacyjną: rekruter pyta "Co nowego w React 19?" - a Ty nie wiesz, że useFormState zmienił nazwę na useActionState, że forwardRef już nie jest potrzebny, i że istnieje hook, który można wywoływać warunkowo. W 2026 roku znajomość React 19 będzie standardem, nie opcją.

React 19 to przełomowa wersja, która wprowadza zupełnie nowe hooki i fundamentalnie zmienia sposób, w jaki obsługujemy formularze, stany asynchroniczne i optymistyczne aktualizacje UI. W tym przewodniku znajdziesz wszystkie nowości z praktycznymi przykładami kodu i gotowymi odpowiedziami w formacie "30 sekund / 2 minuty".

1. Najważniejsze zmiany w React 19 - przegląd

Zanim zagłębimy się w szczegóły, oto szybki przegląd kluczowych nowości:

Nowe Hooki:

  • useActionState - obsługa formularzy z automatycznym pending
  • useOptimistic - optymistyczne aktualizacje UI
  • useFormStatus - status formularza bez prop drilling
  • use() - odczytywanie Promise i Context (warunkowo!)

Zmiany w API:

  • ref jako zwykły prop (koniec z forwardRef)
  • Context jako provider (bez .Provider)
  • Cleanup w ref callback

Server Features:

  • Server Components (stabilne)
  • Server Actions
  • Async Components

2. useActionState - obsługa stanów formularzy

Wyjaśnienie w 30 sekund

useActionState to nowy hook w React 19, który zastępuje kombinację useState + useTransition przy obsłudze formularzy. Przyjmuje funkcję akcji i stan początkowy, zwracając aktualny stan, nową akcję do przekazania do formularza oraz flagę isPending. Automatycznie obsługuje stany ładowania i błędów bez potrzeby ręcznego zarządzania.

Wyjaśnienie w 2 minuty

useActionState rozwiązuje jeden z największych problemów w React - boilerplate przy obsłudze formularzy. Wcześniej potrzebowaliśmy:

// ❌ Stary sposób - React 18
function OldForm() {
  const [state, setState] = useState({ error: null, data: null });
  const [isPending, startTransition] = useTransition();

  const handleSubmit = async (e) => {
    e.preventDefault();
    const formData = new FormData(e.target);
    startTransition(async () => {
      try {
        const result = await submitForm(formData);
        setState({ data: result, error: null });
      } catch (err) {
        setState({ data: null, error: err.message });
      }
    });
  };

  return <form onSubmit={handleSubmit}>...</form>;
}

W React 19 ten sam kod wygląda tak:

// ✅ Nowy sposób - React 19
function NewForm() {
  const [state, formAction, isPending] = useActionState(
    async (previousState, formData) => {
      try {
        const result = await submitForm(formData);
        return { data: result, error: null };
      } catch (err) {
        return { data: null, error: err.message };
      }
    },
    { data: null, error: null }
  );

  return (
    <form action={formAction}>
      {state.error && <p className="error">{state.error}</p>}
      <button disabled={isPending}>
        {isPending ? 'Wysyłanie...' : 'Wyślij'}
      </button>
    </form>
  );
}

Kluczowe zalety:

  • Automatyczne zarządzanie stanem pending
  • Działa z Server Actions (progressive enhancement)
  • Poprzedni stan dostępny w funkcji akcji
  • Formularz działa nawet bez JavaScript (z Server Actions)

Klasyczne pytanie rekrutacyjne

Pytanie: Jaka jest różnica między useActionState a useFormState?

Odpowiedź: useFormState było wcześniejszą nazwą tego hooka w React Canary i react-dom. W stabilnej wersji React 19 nazwa została zmieniona na useActionState i hook przeniesiono do głównego pakietu 'react'. Funkcjonalność jest identyczna, ale useActionState dodatkowo zwraca flagę isPending jako trzeci element tablicy.

3. useOptimistic - optymistyczne aktualizacje UI

Wyjaśnienie w 30 sekund

useOptimistic pozwala pokazać użytkownikowi przewidywany wynik akcji natychmiast, zanim serwer potwierdzi operację. Hook przyjmuje aktualny stan i funkcję aktualizującą, zwracając stan optymistyczny i funkcję do jego ustawienia. Gdy akcja asynchroniczna się zakończy, stan automatycznie wraca do rzeczywistego.

Wyjaśnienie w 2 minuty

Optymistyczne UI to pattern, w którym pokazujemy użytkownikowi efekt jego akcji natychmiast, nie czekając na odpowiedź serwera. To drastycznie poprawia perceived performance aplikacji.

function MessageThread({ messages, sendMessage }) {
  const [optimisticMessages, addOptimisticMessage] = useOptimistic(
    messages,
    (currentMessages, newMessage) => [
      ...currentMessages,
      { ...newMessage, status: 'sending' }
    ]
  );

  const handleSend = async (formData) => {
    const text = formData.get('message');
    const newMessage = { id: Date.now(), text, author: 'me' };

    // Natychmiast pokazujemy wiadomość
    addOptimisticMessage(newMessage);

    // W tle wysyłamy na serwer
    await sendMessage(newMessage);
  };

  return (
    <div>
      {optimisticMessages.map(msg => (
        <Message
          key={msg.id}
          {...msg}
          className={msg.status === 'sending' ? 'opacity-50' : ''}
        />
      ))}
      <form action={handleSend}>
        <input name="message" />
        <button>Wyślij</button>
      </form>
    </div>
  );
}

Kluczowe cechy:

  • Stan optymistyczny automatycznie "znika" po zakończeniu transition
  • Jeśli akcja się nie powiedzie, UI wraca do poprzedniego stanu
  • Można pokazać różne style dla elementów optymistycznych (np. opacity)

Klasyczne pytanie rekrutacyjne

Pytanie: Jak obsłużyć błąd w useOptimistic?

Odpowiedź: useOptimistic automatycznie obsługuje błędy - jeśli Promise w transition zostanie odrzucony, stan wraca do ostatniego "prawdziwego" stanu. Nie ma dedykowanego callback'a na błędy, więc jeśli chcemy pokazać komunikat błędu, powinniśmy użyć try/catch w akcji i osobnego stanu na błędy lub połączyć z useActionState.

4. useFormStatus - status formularza bez prop drilling

Wyjaśnienie w 30 sekund

useFormStatus to hook z react-dom, który pozwala komponentom potomnym formularza odczytać jego status (pending, data, method, action) bez przekazywania props. Musi być wywołany wewnątrz komponentu renderowanego w <form>. Zwraca obiekt z flagą pending i danymi formularza.

Wyjaśnienie w 2 minuty

useFormStatus rozwiązuje problem prop drilling przy budowaniu reużywalnych komponentów formularzy:

// ❌ Problem: prop drilling
function Form() {
  const [isPending, setIsPending] = useState(false);
  return (
    <form>
      <Input isPending={isPending} />
      <SubmitButton isPending={isPending} />
    </form>
  );
}

// ✅ Rozwiązanie: useFormStatus
import { useFormStatus } from 'react-dom';

function SubmitButton() {
  const { pending, data, method, action } = useFormStatus();

  return (
    <button type="submit" disabled={pending}>
      {pending ? 'Wysyłanie...' : 'Wyślij'}
    </button>
  );
}

function Form() {
  return (
    <form action={submitAction}>
      <Input />
      <SubmitButton /> {/* Nie potrzebuje props! */}
    </form>
  );
}

Obiekt zwracany przez useFormStatus:

Właściwość Typ Opis
pending boolean Czy formularz jest w trakcie wysyłania
data FormData | null Dane formularza podczas wysyłania
method string Metoda HTTP (get/post)
action function | string Akcja formularza

Ważne ograniczenie: useFormStatus odczytuje status tylko rodzica <form>. Nie działa dla formularzy w tym samym komponencie lub komponentach nadrzędnych.

// ❌ Nie zadziała - useFormStatus w tym samym komponencie co form
function BrokenForm() {
  const { pending } = useFormStatus(); // Zawsze zwróci pending: false!
  return <form action={action}>...</form>;
}

// ✅ Zadziała - useFormStatus w komponencie potomnym
function WorkingForm() {
  return (
    <form action={action}>
      <StatusAwareButton /> {/* useFormStatus tutaj */}
    </form>
  );
}

5. Hook use() - odczytywanie Promise i Context

Wyjaśnienie w 30 sekund

use() to nowy hook, który może odczytywać wartości z Promise lub Context. W przeciwieństwie do innych hooków, może być wywoływany warunkowo (w if/else). Gdy przekażemy Promise, React zawiesi komponent do czasu jego rozwiązania (wymaga Suspense). Gdy przekażemy Context, działa jak useContext ale można go używać warunkowo.

Wyjaśnienie w 2 minuty

Hook use() to rewolucja w React, bo łamie fundamentalną zasadę hooków - można go wywoływać warunkowo:

import { use, Suspense } from 'react';

// Użycie z Promise
function UserProfile({ userPromise }) {
  const user = use(userPromise); // Zawiesza do rozwiązania Promise
  return <h1>{user.name}</h1>;
}

function App() {
  const userPromise = fetchUser(userId);
  return (
    <Suspense fallback={<Spinner />}>
      <UserProfile userPromise={userPromise} />
    </Suspense>
  );
}

Warunkowe użycie use():

function AdminPanel({ user, isAdmin }) {
  // ✅ Można wywoływać warunkowo!
  if (isAdmin) {
    const adminData = use(AdminContext);
    return <AdminDashboard data={adminData} />;
  }

  return <RegularDashboard />;
}

Kluczowe różnice:

Cecha useContext use(Context) use(Promise)
Warunkowe wywołanie Nie Tak Tak
Suspense Nie Nie Tak
Server Components Nie Nie Tak

Klasyczne pytanie rekrutacyjne

Pytanie: Kiedy użyć use() zamiast useContext?

Odpowiedź: use() z Context jest preferowany gdy potrzebujemy odczytać context warunkowo - w bloku if/else lub w early return. useContext musi być zawsze wywoływany na górze komponentu. Poza tym funkcjonalność jest identyczna. use() jest też jedynym sposobem na odczytanie Promise bezpośrednio w komponencie z Suspense.

6. ref jako prop - koniec z forwardRef

Wyjaśnienie w 30 sekund

W React 19 ref może być przekazywany jako zwykły prop do komponentów funkcyjnych, bez potrzeby używania forwardRef. To znacznie upraszcza kod komponentów, które muszą przekazywać ref do elementów DOM. forwardRef nadal działa dla kompatybilności wstecznej, ale nie jest już potrzebny w nowym kodzie.

Wyjaśnienie w 2 minuty

Porównanie starego i nowego podejścia:

// ❌ React 18 - wymaga forwardRef
const Input = forwardRef(function Input(props, ref) {
  return <input ref={ref} {...props} />;
});

// ✅ React 19 - ref jako zwykły prop
function Input({ ref, ...props }) {
  return <input ref={ref} {...props} />;
}

// Użycie identyczne w obu przypadkach
function Form() {
  const inputRef = useRef(null);
  return <Input ref={inputRef} placeholder="Email" />;
}

Dodatkowa nowość - cleanup w ref callback:

// React 19 - ref callback może zwracać funkcję cleanup
function MeasuredComponent() {
  return (
    <div ref={(node) => {
      if (node) {
        // Setup - element zamontowany
        const observer = new ResizeObserver(() => {
          console.log('Rozmiar zmieniony');
        });
        observer.observe(node);

        // Cleanup - zwracamy funkcję
        return () => observer.disconnect();
      }
    }}>
      Treść
    </div>
  );
}

7. Context jako provider - uproszczona składnia

Wyjaśnienie w 30 sekund

W React 19 Context może być renderowany bezpośrednio jako provider bez używania <Context.Provider>. Zamiast <ThemeContext.Provider value={theme}> piszemy <ThemeContext value={theme}>. To skraca kod i poprawia czytelność. <Context.Provider> nadal działa, ale zostanie usunięty w przyszłej wersji.

Wyjaśnienie w 2 minuty

const ThemeContext = createContext('light');

// ❌ Stary sposób - React 18
function OldApp() {
  return (
    <ThemeContext.Provider value="dark">
      <MainContent />
    </ThemeContext.Provider>
  );
}

// ✅ Nowy sposób - React 19
function NewApp() {
  return (
    <ThemeContext value="dark">
      <MainContent />
    </ThemeContext>
  );
}

Ta zmiana jest częścią szerszego trendu w React 19 - upraszczania API i redukcji boilerplate.

8. Server Components i Server Actions

Wyjaśnienie w 30 sekund

Server Components to komponenty React renderowane na serwerze, które nigdy nie trafiają do bundle'a klienta. Mogą bezpośrednio odczytywać dane z bazy, plików lub API bez dodatkowych endpointów. Server Actions to funkcje async oznaczone 'use server', które można wywoływać z klienta - React automatycznie tworzy endpoint i obsługuje komunikację.

Wyjaśnienie w 2 minuty

// Server Component - renderowany tylko na serwerze
async function ProductList() {
  // Bezpośredni dostęp do bazy danych!
  const products = await db.products.findMany();

  return (
    <ul>
      {products.map(p => <ProductCard key={p.id} product={p} />)}
    </ul>
  );
}

// Server Action
async function addToCart(productId) {
  'use server';

  const user = await getCurrentUser();
  await db.cart.add({ userId: user.id, productId });
  revalidatePath('/cart');
}

// Client Component używający Server Action
'use client';
function AddToCartButton({ productId }) {
  return (
    <form action={addToCart.bind(null, productId)}>
      <button type="submit">Dodaj do koszyka</button>
    </form>
  );
}

9. Praktyczne zadania rekrutacyjne z React 19

Zadanie 1: Zaimplementuj formularz z walidacją używając useActionState

'use client';
import { useActionState } from 'react';

async function registerUser(prevState, formData) {
  const email = formData.get('email');
  const password = formData.get('password');

  // Walidacja
  const errors = {};
  if (!email?.includes('@')) {
    errors.email = 'Nieprawidłowy email';
  }
  if (password?.length < 8) {
    errors.password = 'Hasło musi mieć min. 8 znaków';
  }

  if (Object.keys(errors).length > 0) {
    return { errors, success: false };
  }

  // Wysyłka na serwer
  try {
    await api.register({ email, password });
    return { errors: {}, success: true };
  } catch (err) {
    return { errors: { form: err.message }, success: false };
  }
}

function RegistrationForm() {
  const [state, formAction, isPending] = useActionState(
    registerUser,
    { errors: {}, success: false }
  );

  if (state.success) {
    return <p>Rejestracja zakończona! Sprawdź email.</p>;
  }

  return (
    <form action={formAction}>
      <div>
        <input name="email" type="email" placeholder="Email" />
        {state.errors.email && (
          <span className="error">{state.errors.email}</span>
        )}
      </div>

      <div>
        <input name="password" type="password" placeholder="Hasło" />
        {state.errors.password && (
          <span className="error">{state.errors.password}</span>
        )}
      </div>

      {state.errors.form && (
        <div className="error">{state.errors.form}</div>
      )}

      <button disabled={isPending}>
        {isPending ? 'Rejestracja...' : 'Zarejestruj się'}
      </button>
    </form>
  );
}

Zadanie 2: Stwórz optymistyczny like button

'use client';
import { useOptimistic, useActionState } from 'react';

function LikeButton({ postId, initialLikes, isLiked: initialIsLiked }) {
  const [{ likes, isLiked }, formAction, isPending] = useActionState(
    async (prev, formData) => {
      const action = formData.get('action');
      const newIsLiked = action === 'like';

      await api.toggleLike(postId, newIsLiked);

      return {
        likes: prev.likes + (newIsLiked ? 1 : -1),
        isLiked: newIsLiked
      };
    },
    { likes: initialLikes, isLiked: initialIsLiked }
  );

  const [optimistic, setOptimistic] = useOptimistic(
    { likes, isLiked },
    (current, newIsLiked) => ({
      likes: current.likes + (newIsLiked ? 1 : -1),
      isLiked: newIsLiked
    })
  );

  return (
    <form action={(formData) => {
      const newIsLiked = formData.get('action') === 'like';
      setOptimistic(newIsLiked);
      formAction(formData);
    }}>
      <input type="hidden" name="action" value={optimistic.isLiked ? 'unlike' : 'like'} />
      <button type="submit">
        {optimistic.isLiked ? '❤️' : '🤍'} {optimistic.likes}
      </button>
    </form>
  );
}

Kiedy NIE używać nowych hooków React 19?

useActionState:

  • Proste formularze bez walidacji serwerowej
  • Gdy potrzebujesz pełnej kontroli nad stanem (wiele etapów, wizard)

useOptimistic:

  • Operacje, które często failują (nie chcemy "skakania" UI)
  • Gdy nie możemy przewidzieć wyniku (np. generowanie AI)

useFormStatus:

  • Formularze z jednym przyciskiem w tym samym komponencie
  • Gdy już masz isPending z useActionState

10. Migracja z React 18 do React 19

Checklist migracji

  1. Zaktualizuj zależności:
npm install react@19 react-dom@19
  1. Zamień useFormState na useActionState:
// ❌ Stare
import { useFormState } from 'react-dom';
const [state, action] = useFormState(fn, init);

// ✅ Nowe
import { useActionState } from 'react';
const [state, action, isPending] = useActionState(fn, init);
  1. Usuń forwardRef (opcjonalnie):
// ❌ Stare
const Input = forwardRef((props, ref) => <input ref={ref} {...props} />);

// ✅ Nowe
function Input({ ref, ...props }) {
  return <input ref={ref} {...props} />;
}
  1. Uprość Context.Provider:
// ❌ Stare
<ThemeContext.Provider value={theme}>

// ✅ Nowe
<ThemeContext value={theme}>

Na Co Rekruterzy Zwracają Uwagę

Po przeprowadzeniu wielu rozmów rekrutacyjnych mogę powiedzieć, że przy pytaniach o React 19 rekruterzy sprawdzają:

Czy znasz nazewnictwo - zamiana useFormState na useActionState to częsta pułapka. Kandydat, który mówi o "useFormState z react-dom" pokazuje, że nie śledzi aktualnych zmian.

Czy rozumiesz trade-offy - kiedy NIE używać useOptimistic? Kiedy forwardRef nadal ma sens? Dobre odpowiedzi pokazują praktyczne doświadczenie.

Czy potrafisz wyjaśnić dlaczego - nie tylko CO robi use(), ale DLACZEGO może być wywoływany warunkowo (bo nie zarządza stanem jak inne hooki).

Zobacz też


Chcesz Więcej Pytań z React?

Ten artykuł to tylko wierzchołek góry lodowej. Mamy ponad 200 pytań z React z szczegółowymi odpowiedziami, przykładami kodu i wyjaśnieniami - od podstaw po zaawansowane tematy jak Server Components, Concurrent Features i optymalizacja wydajności.

Sprawdź Pełny Zestaw Pytań React →

Lub wypróbuj nasz darmowy podgląd pytań React, żeby zobaczyć więcej pytań w tym stylu.


Napisane przez zespół Flipcards, na podstawie oficjalnej dokumentacji React 19 i doświadczeń z rozmów rekrutacyjnych w firmach takich jak Meta, Vercel i wiodących startupach technologicznych.

Chcesz więcej pytań rekrutacyjnych?

To tylko jeden temat z naszego kompletnego przewodnika po rozmowach rekrutacyjnych. Uzyskaj dostęp do 800+ pytań z 13 technologii.

Kup pełny dostęp Zobacz bezpłatny podgląd
Powrót do blogu

Zostaw komentarz

Pamiętaj, że komentarze muszą zostać zatwierdzone przed ich opublikowaniem.