TanStack Query (React Query): Handling HTTP Requests with EaseEntry 2 of 10
Fetching Data with useQuery
useQuery runs a query function, caches its result under a queryKey, and returns the request's state.
Notes
queryKey: an array that identifies the data in the cache, e.g.['events']. Same key = same cached data, across all components.queryFn: the function that fetches. It must return a Promise and throw on failure; otherwise TanStack Query treats the response as success.- Returned values:
| Value | Meaning |
|---|---|
data | What queryFn returned (undefined until loaded) |
isPending | No data yet; the first request is on its way |
isFetching | Any request in progress, including background refetches |
isError | true if queryFn threw |
error | The thrown error |
refetch | Call manually to run the same query again |
- Failed queries retry 3 times by default before
isErrorbecomestrue.
What / Why / When
- What
Declarative data fetching.
- Why
Loading, error, caching, and refetching handled in one hook.
- When
Any GET request whose result a component displays.
Code compare
// components/NewEventsSection.tsximport { useQuery } from "@tanstack/react-query";import { fetchEvents } from "../services/events";export default function NewEventsSection() { const { data, isPending, isError, error, refetch } = useQuery({ queryKey: ["events", { max: 3 }], queryFn: ({ signal }) => fetchEvents({ signal, max: 3 }), }); if (isPending) return <LoadingIndicator />; if (isError) { return ( <> <ErrorBlock title="An error occurred" message={error.message || "Failed to fetch events."} /> <button onClick={() => refetch()}>Try again</button> </> ); } return ( <ul className="events-list"> {data.map((event) => ( <li key={event.id}> <EventItemCard event={event} /> </li> ))} </ul> );}// new-events-section.component.ts@Component({ selector: "app-new-events-section", imports: [EventItemCardComponent, LoadingIndicatorComponent, ErrorBlockComponent], template: ` @if (events.isLoading()) { <app-loading-indicator /> } @else if (events.error()) { <app-error-block title="An error occurred" message="Failed to fetch events." /> <button (click)="events.reload()">Try again</button> } @else { <ul class="events-list"> @for (event of events.value(); track event.id) { <li><app-event-item-card [event]="event" /></li> } </ul> } `,})export class NewEventsSectionComponent { private eventsService = inject(EventsService); protected events = rxResource({ stream: () => this.eventsService.getEvents({ max: 3 }), defaultValue: [], });}Differences and gotchas
- The course notes call it
refresh; the correct name isrefetch. Angular's equivalent isreload(). isPendingistrueonly before the first data arrives. UseisFetchingto show a background-refresh indicator.rxResourcestate belongs to the component instance; there's no shared cache unless you add one (Entry 3).- In Angular before v20,
rxResourceusedrequest/loaderinstead ofparams/stream.
Quick reference
| React | Angular |
|---|---|
useQuery({ queryKey, queryFn }) | rxResource({ params, stream }) |
data | value() |
isPending | isLoading() (first load) |
isError / error | error() |
refetch() | reload() |