Jeśli zdarzyło Wam się pisać w React komponent, który pobiera dane z API, to pewnie dobrze znacie ten schemat. Trzy stany, useEffect, obsługa błędów, obsługa ładowania… i to wszystko zanim jeszcze dojdziemy do właściwej logiki komponentu.
Przykład:
function UserListClassic({ url }) {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
fetch(url)
.then(res => {
if (!res.ok) {
throw new Error(`HTTP ${res.status}`)
}
return res.json()
})
.then(json => {
setData(json)
setLoading(false)
})
.catch(err => {
setError(err.message)
setLoading(false)
})
}, [url])
if (loading) return <Spinner />
if (error) return <ErrorMsg error={error} />
return <UserList data={data} />
}
Około 27 linii, z czego większość to zarządzanie stanem – a nie faktyczna praca komponentu. Nie jest to nic złego, ale React 19 przynosi nam sposób na pozbycie się tego całego boilerplate’u.
Czym jest use?
use to nowe API wprowadzone w React 19, które pozwala odczytać wartość z Promise lub z kontekstu bezpośrednio w ciele komponentu.
const value = use(resource)
W odróżnieniu od hooków, use można wywoływać warunkowo – w pętlach, w blokach if, w różnych miejscach komponentu. To wyjątek od reguły hooków i jedna z rzeczy, które wyróżniają use spośród pozostałych API Reacta.
Kiedy przekazujemy mu Promise, React “zawiesza” renderowanie komponentu do czasu, aż dane będą gotowe. Jeśli fetch zwróci błąd, React sam go przechwytuje i przekazuje do najbliższego Error Boundary. Zamiast samodzielnie obsługiwać oba przypadki w komponencie, delegujemy to do Reacta.
Ważne: use nie zastępuje useEffect w ogólności. Rozwiązuje konkretny scenariusz – “komponent czeka na Promise”. Jeśli potrzebujesz efektów ubocznych, subskrypcji, timerów czy czegokolwiek innego niezwiązanego z oczekiwaniem na dane – useEffect nadal jest właściwym narzędziem.
Jak to wygląda w praktyce?
Zobaczcie jak wygląda ten sam komponent przepisany z użyciem use:
function UserListModern({ promise }) {
const data = use(promise)
return <UserList data={data} />
}
Trzy linie!
Komponent zajmuje się tylko tym, co ma wyświetlić – nie interesuje go ani ładowanie, ani błędy. Te dwa przypadki obsługuje jego kontener:
<ErrorBoundary> {/* ← obsługuje błąd */}
<Suspense fallback={...}> {/* ← obsługuje ładowanie */}
<UserListModern promise={promise} />
</Suspense>
</ErrorBoundary>
<Suspense> wyświetli fallback dopóki Promise się nie rozwiąże, a <ErrorBoundary> przechwytuje błędy z odrzuconego Promise.
Demo z projektu
Stworzyłam mały projekt na Github, który pokazuje oba podejścia obok siebie.

Po lewej klasyczny useState + useEffect, po prawej use z Suspense. Oba pobierają te same dane z tego samego URL, a różnica jest wyłącznie w tym, kto zarządza stanami.
Żeby to zobaczyć, wystarczy zajrzeć do dwóch komponentów: WithUseState.tsx i WithUse.tsx
Kilka ważnych szczegółów
Jak wygląda minimalny ErrorBoundary?
ErrorBoundary to nadal class component – React nie udostępnia jeszcze wbudowanego komponentu tego typu, więc trzeba go napisać samodzielnie (albo użyć paczki react-error-boundary):
class ErrorBoundary extends React.Component {
state = { hasError: false, error: null }
static getDerivedStateFromError(error) {
return { hasError: true, error }
}
render() {
if (this.state.hasError) {
return this.props.fallback ?? <p>Coś poszło nie tak.</p>
}
return this.props.children
}
}
Jeśli nie chcecie pisać tego ręcznie, polecam gotową paczkę react-error-boundary, która daje też wygodny hook useErrorBoundary.
Promise musi być stabilny
Promise trzeba tworzyć poza komponentem (albo memoizować), ponieważ jeśli będziemy go tworzyć bezpośrednio w render, React za każdym renderowaniem dostanie nowy Promise i będzie wpadać w nieskończoną pętlę. W demo powyżej Promise jest tworzony w komponencie-rodzicu i przekazywany jako props – to dobre podejście.
Jeśli jednak musisz tworzyć Promise wewnątrz komponentu, użyj useMemo.
function ParentComponent({ url }) {
const promise = useMemo(() => fetch(url).then(res => res.json()), [url])
return (
<ErrorBoundary fallback={<ErrorMsg />}>
<Suspense fallback={<Spinner />}>
<UserListModern promise={promise} />
</Suspense>
</ErrorBoundary>
)
}
use działa też z kontekstem
use można stosować zamiast useContext – z jedną istotną różnicą: w odróżnieniu od useContext, wywołanie use(MyContext) może pojawić się warunkowo:
// klasycznie – tylko na poziomie komponentu, nigdy warunkowo
const theme = useContext(ThemeContext)
// z use – można warunkowo
function MyComponent({ applyTheme }) {
if (applyTheme) {
const theme = use(ThemeContext)
return <div style={{ color: theme.primary }}>...</div>
}
return <div>...</div>
}
Czy warto przejść na use?
To zależy od projektu. Jeśli używacie React 19 i macie już <Suspense> w swojej architekturze (albo planujecie ją wprowadzić), use znacznie upraszcza komponenty i sprawia, że każdy z nich odpowiada za jedną rzecz – wyświetlanie danych, a nie zarządzanie ich stanem pobierania.
Jeśli macie React ≤ 18 albo potrzebujecie bardziej szczegółowej kontroli nad stanami ładowania w samym komponencie – klasyczne podejście nadal działa i nie ma nic złego w jego używaniu:)
Pełna dokumentacja jest dostępna na https://react.dev/reference/react/use.


