Tanstack Query je moderní state manager pro TS / JS aplikace, se skvělými funkcemi na vylepšení DX ale i zkvalitnění UX skrz spolehlivější načítání dat.
Tady odkazy na dokumentaci, na přehled funkcí, na oficiální studijní kurz (placený), a na kurz na internetu zdarma.
Vlastními slovy se popisuje:
Powerful asynchronous state management for TS/JS, React, Solid, Vue, Svelte and Angular
TanStack Query (někteří si možná pamatují pod názvem React Query) ti usnadní práci načítání dat z API, správu loading a error stavů, cachování nebo refetching. Už žádné ruční useEffect, loading stavy, ani složité cachování.
Co Tanstack Query umí:
- načítáni dat (
useQuery) - automaticky spravuje loading/error stavy
- ukládá a sdílí data v cache
- refetchuje podle potřeby - po znovunačtení okna, změně focusu apod.
- mutace (
useMutation)
a navíc v jednom balíčku poskytuje také:
- Tanstack Query Devtools – realtime náhled na cache a dotazy
- SSR & React Native podpora
Ukázka nejjednoduššího použití Tanstack Query
Podívejme se na oficiální ukázku
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query'
const queryClient = new QueryClient()
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<Example />
</QueryClientProvider>
)
}
function Example() {
const { isPending, error, data } = useQuery({
queryKey: ['repoData'],
queryFn: () =>
fetch('https://api.github.com/repos/TanStack/query').then((res) =>
res.json(),
),
})
if (isPending) return 'Loading...'
if (error) return 'An error has occurred: ' + error.message
return (
<div>
<h1>{data.name}</h1>
<p>{data.description}</p>
<strong>👀 {data.subscribers_count}</strong>{' '}
<strong>✨ {data.stargazers_count}</strong>{' '}
<strong>🍴 {data.forks_count}</strong>
</div>
)
}- nainstaluješ a zabalíš svoji aplikaci do
QueryProvidera hotovo, je to funkční - v komponentě využiješ
useQuery, které odevzdáš funkci která fetchuje tvoje data, o vše ostatní se stará React Query, a také klíč díky kterému bude hlídat aktuální data - tento hook ti poskytne vše co v komponentě potřebuješ:
loading,isPendingneboerror( a hromadu dalších pomocníků, viz dokumentaci) - jednoduše vyrenderuješ na základě stavu query různé UI
- když data šťastně dorazí, tak renderuješ data
Podívejme se ale i na podrobnější příklad jak v praxi využít tento super nástroj.
Ukázka z praxe
Prakticky je velmi příjemné si držet všechny fetch funkce na jednom místě jako api.ts nebo query.ts, následně doporučované a vynikající DX je využívat custom hooks, které složíme pomocí Query funkcí. Odstraníme tím hromadu boilerplate a komponenty jsou pak mnohem čistší a mnoho z nich se stane takřka dumb komponentami.
// queries.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
export type Restaurant = {
id: string;
name: string;
address: string;
};
// Fetcher: načte seznam restaurací
// klidně využívej fetchovací knihovnu jako `axios` nebo `ky`
const fetchRestaurants = async (): Promise<Restaurant[]> => {
const res = await fetch('/api/restaurants');
if (!res.ok) throw new Error('Chyba při načítání restaurací');
return res.json();
};
// Fetcher: vytvoří novou restauraci
const postRestaurant = async (data: Omit<Restaurant, 'id'>): Promise<Restaurant> => {
const res = await fetch('/api/restaurants', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!res.ok) throw new Error('Chyba při přidávání restaurace');
return res.json();
};
// Custom hook pro načítání seznamu
// vrátí objekt s daty (nebo null) a různými pomocníky typu boolean nebo Error, které automaticky sleduje, trackuje a ty na základě nich můžeš jen renderovat co potřebuješ
// např. data, isError, error, isPending, loading, status, isFetched, isStale, refetch
export const useRestaurants = () => {
return useQuery<Restaurant[]>({
queryKey: ['restaurants'], // VELICE důležité!!
queryFn: fetchRestaurants,
staleTime: 2 * 1000 // čas (v ms) který se data budou vracet z cache, místo nového volání API
});
}
// Custom hook pro mutaci (vytvoření nové restaurace)
export const useAddRestaurant = () => {
const queryClient = useQueryClient();
// vrátí metody na správu akce POST (nebo jakékoliv jiné) mutation dle poskytnuté logiky
return useMutation({
mutationFn: postRestaurant,
onSuccess: () => {
// Po úspěšném přidání invaliduj seznam, aby se znovu načetl
// TADY využiješ ten klíč, který si poskytoval původní useQuery funkci, a díky nemu jsou query provázané
// doporučuji držet všechny `queryKeys` na jednom místě jako read-only objekt nebo enum
queryClient.invalidateQueries({ queryKey: ['restaurants'] });
// když je hotovo, seznam se refetchuje z API (díky invalidaci) a RestaurantList se rerenderuje
},
});
};// RestaurantList.tsx
import { useRestaurants } from './queries';
export const RestaurantList = () => {
const { data, isLoading, error } = useRestaurants();
if (isLoading) return <p>Načítání restaurací...</p>;
if (error) return <p>Chyba: {(error as Error).message}</p>;
return (
<ul>
{data?.map((r) => (
<li key={r.id}>
<strong>{r.name}</strong> – {r.address}
</li>
))}
</ul>
);
};
// AddRestaurantForm.tsx
import { useState } from 'react';
import { useAddRestaurant } from './queries';
export const AddRestaurantForm = () => {
const [name, setName] = useState('');
const [address, setAddress] = useState('');
const mutation = useAddRestaurant(); // tady metody na správu `action`, ale i pomocníci s info o stavu query
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!name || !address) return;
mutation.mutate({ name, address });
setName('');
setAddress('');
};
return (
<form onSubmit={handleSubmit}>
<input type="text"
placeholder="Název restaurace"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input type="text"
placeholder="Adresa"
value={address}
onChange={(e) => setAddress(e.target.value)}
/>
<button type="submit" disabled={mutation.isPending}>
{mutation.isPending ? 'Přidávám...' : 'Přidat'}
</button>
{mutation.error && (
<p style={{ color: 'red' }}>
{(mutation.error as Error).message}
</p>
)}
</form>
);
};// App.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { RestaurantList } from './RestaurantList';
import { AddRestaurantForm } from './AddRestaurantForm';
const queryClient = new QueryClient();
export const App = () => {
return (
<QueryClientProvider client={queryClient}>
<h1>Restaurace</h1>
<AddRestaurantForm />
<RestaurantList />
</QueryClientProvider>
);
};Další vlastnosti
Díky Tanstack Query můžeme relativně jednoduše, ale s výborným DX, zpracovávat i Infinite Scrolling nebo Optimistic updates, pro víc info koukni na useInfiniteQuery nebo na termín Optimistic updates v dokumentaci. A ještě stále je možné říct, že Tanstack Query toho dokáže mnohem mnohem víc.