Co nowego w React 19? Kompletny przewodnik po nowych hookach na rozmowie rekrutacyjnej w 2026 roku
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:
-
refjako 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
- Zaktualizuj zależności:
npm install react@19 react-dom@19
- 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);
- Usuń forwardRef (opcjonalnie):
// ❌ Stare
const Input = forwardRef((props, ref) => <input ref={ref} {...props} />);
// ✅ Nowe
function Input({ ref, ...props }) {
return <input ref={ref} {...props} />;
}
- 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ż
- 15 Najtrudniejszych Pytań Rekrutacyjnych z React - fundamentalne pytania o React, które nadal są aktualne w wersji 19
- React Hooks: useEffect, useMemo, useCallback - Kiedy NIE używać? - głębokie zrozumienie istniejących hooków przed nauką nowych
- Najtrudniejsze Pytania z Frameworków JavaScript - React, Next.js, i inne frameworki w kontekście React 19
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.
