TanStack Query — kesh, qayta yuklash, mutatsiyalar
Muammodan boshlaylik
O'tgan darsda bitta ro'yxat uchun 40 qator kod yozdik. Endi tasavvur qiling:
- Katalogdan mahsulotga o'tib, qaytdingiz — ro'yxat qaytadan yuklanmoqda (spinner)
- Mahsulot ekranini ochganda — ro'yxatda allaqachon bor ma'lumot yana so'raladi
- Savatga qo'shdingiz — tab badge va savat ekrani o'zini yangilamaydi
- Uchta ekran bir xil "profil" ni so'raydi — uchta so'rov
- Ilova fondan qaytganda ma'lumot eskirgan
Bularning hammasi — server holati muammolari. TanStack Query (avvalgi nomi React Query) ularni avtomatik hal qiladi.
O'rnatish
npm install @tanstack/react-query
// app/_layout.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60_000, // 1 daqiqa — ma'lumot "yangi" hisoblanadi
retry: 2, // xatoda 2 marta qayta urinish
},
},
});
export default function RootLayout() {
return (
<QueryClientProvider client={queryClient}>
<Stack />
</QueryClientProvider>
);
}
useQuery — ma'lumot o'qish
O'tgan darsdagi 40 qator:
import { useQuery } from '@tanstack/react-query';
import { apiGet } from '@/lib/api';
export default function Katalog() {
const { data, isPending, isError, error, refetch, isRefetching } = useQuery({
queryKey: ['mahsulotlar'],
queryFn: () => apiGet<Mahsulot[]>('/mahsulotlar'),
});
if (isPending) return <ActivityIndicator size="large" style={{ flex: 1 }} />;
if (isError) return <XatoEkran xabar={error.message} qaytaUrinish={refetch} />;
return (
<FlatList
data={data}
keyExtractor={(m) => String(m.id)}
renderItem={({ item }) => <MahsulotKarta mahsulot={item} />}
refreshing={isRefetching}
onRefresh={refetch}
/>
);
}
queryKey— ma'lumotning noyob nomi. Bir xil kalit — bir xil keshqueryFn— ma'lumotni olish funksiyasiisPending— birinchi yuklanish;isRefetching— fonda yangilanish- Bekor qilish, qayta urinish, takroriy so'rovlarni birlashtirish — avtomatik
Keshning ishlashi
1. Katalog ochildi → so'rov, natija keshga: ['mahsulotlar']
2. Mahsulotga o'tildi
3. Katalogga qaytildi → keshdan DARHOL ko'rsatiladi (spinner yo'q)
→ staleTime o'tgan bo'lsa — fonda jimgina yangilanadi
Foydalanuvchi hech qachon bo'sh ekranni kutmaydi — eski ma'lumotni darhol ko'radi, yangisi fonda keladi. Bu stale-while-revalidate.
Parametrli so'rovlar
function useMahsulot(id: string) {
return useQuery({
queryKey: ['mahsulot', id], // id — kalitning qismi
queryFn: () => apiGet<Mahsulot>(`/mahsulotlar/${id}`),
enabled: !!id, // id bo'lmasa — so'ramaslik
});
}
function useKatalog(toifa: string, saralash: string) {
return useQuery({
queryKey: ['mahsulotlar', { toifa, saralash }], // filtr o'zgarsa — yangi kesh
queryFn: () => apiGet<Mahsulot[]>(`/mahsulotlar?toifa=${toifa}&saralash=${saralash}`),
});
}
Har bir filtr kombinatsiyasi — alohida kesh. "Telefonlar" dan "Noutbuklar" ga o'tib, qaytsangiz — telefonlar darhol ko'rinadi.
Custom hook lar — useMahsulot, useKatalog — qoida: so'rov mantig'i ekranlarda emas, hooks/ yoki api/ da.
useMutation — ma'lumot o'zgartirish
import { useMutation, useQueryClient } from '@tanstack/react-query';
function useSavatgaQosh() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (d: { mahsulotId: number; soni: number }) => apiPost('/savat', d),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['savat'] }); // savatni qayta yuklash
},
});
}
// Ekranda
const qosh = useSavatgaQosh();
<Tugma
matn="Savatga qo'shish"
yuklanmoqda={qosh.isPending}
onPress={() => qosh.mutate({ mahsulotId: mahsulot.id, soni: 1 }, {
onSuccess: () => Alert.alert("Qo'shildi"),
onError: (e) => Alert.alert('Xato', e.message),
})}
/>
invalidateQueries(['savat']) — "savat haqidagi kesh eskirdi". Savat ekrani, tab badge — ['savat'] dan foydalanadigan hamma joy avtomatik yangilanadi. Qo'lda hech narsa uzatish shart emas.
Optimistik yangilash
Serverdan javob kutmasdan, darhol interfeysni yangilash — "yoqtirish" tugmasi kabi:
useMutation({
mutationFn: (id: number) => apiPost(`/mahsulotlar/${id}/sevimli`, {}),
onMutate: async (id) => {
await queryClient.cancelQueries({ queryKey: ['mahsulot', String(id)] });
const oldingi = queryClient.getQueryData<Mahsulot>(['mahsulot', String(id)]);
queryClient.setQueryData<Mahsulot>(['mahsulot', String(id)], (m) => m && { ...m, sevimli: !m.sevimli });
return { oldingi };
},
onError: (_e, id, ctx) => {
queryClient.setQueryData(['mahsulot', String(id)], ctx?.oldingi); // xato — qaytarish
},
onSettled: (_d, _e, id) => {
queryClient.invalidateQueries({ queryKey: ['mahsulot', String(id)] });
},
});
Yurak darhol qizaradi; server xato qaytarsa — qaytib oqaradi.
Cheksiz scroll: useInfiniteQuery
20-darsdagi qo'lda yozilgan mantiq:
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, refetch, isRefetching } = useInfiniteQuery({
queryKey: ['yangiliklar'],
queryFn: ({ pageParam }) => apiGet<{ items: Yangilik[]; keyingi: number | null }>(`/yangiliklar?kursor=${pageParam}`),
initialPageParam: 0,
getNextPageParam: (oxirgi) => oxirgi.keyingi, // null — boshqa sahifa yo'q
});
const yangiliklar = data?.pages.flatMap((p) => p.items) ?? [];
<FlatList
data={yangiliklar}
onEndReached={() => hasNextPage && !isFetchingNextPage && fetchNextPage()}
onEndReachedThreshold={0.5}
ListFooterComponent={isFetchingNextPage ? <ActivityIndicator /> : null}
refreshing={isRefetching}
onRefresh={refetch}
/>
React Native ga xos sozlamalar
Vebda TanStack Query oyna fokusga kelganda ma'lumotni yangilaydi. Mobil ilovada "oyna" yo'q — lekin ilova fondan qaytadi:
// app/_layout.tsx
import { AppState, Platform } from 'react-native';
import { focusManager, onlineManager } from '@tanstack/react-query';
import NetInfo from '@react-native-community/netinfo';
// Ilova fondan qaytganda — eskirgan so'rovlarni yangilash
useEffect(() => {
const sub = AppState.addEventListener('change', (holat) => {
if (Platform.OS !== 'web') focusManager.setFocused(holat === 'active');
});
return () => sub.remove();
}, []);
// Internet qaytganda — avtomatik qayta urinish
onlineManager.setEventListener((setOnline) =>
NetInfo.addEventListener((s) => setOnline(!!s.isConnected))
);
Ekranga qaytganda yangilash (26-darsdagi useFocusEffect muammosi) — TanStack Query staleTime bilan buni ko'pincha o'zi hal qiladi.
Nima uchun shunday?
Nima uchun server ma'lumotini oddiy useState yoki Zustand da saqlamaslik kerak?
Chunki server ma'lumoti — sizniki emas. U boshqa joyda yashaydi, boshqalar uni o'zgartiradi, u eskiradi. Uni boshqarish — keshlash, qachon yangilash, takroriy so'rovlarni birlashtirish, xatoda qayta urinish, sahifalash — murakkab va har bir ilovada bir xil muammo. Klient holati (tanlangan tab, ochiq modal, savat qoralamasi) esa butunlay sizniki va oddiy. TanStack Query ikkalasini ajratadi: server holati — Query da, klient holati — useState / Zustand da (keyingi dars).
Xulosa
Eslab qoling:
QueryClientProvider— ildizda;staleTime,retry— umumiy sozlamalaruseQuery({ queryKey, queryFn })—isPending,isError,data,refetchqueryKeyga barcha parametrlar kiradi;enabled— shartli so'rov- Kesh: darhol eski ma'lumot, fonda yangilanish
useMutation+invalidateQueries— o'zgarishdan keyin bog'liq joylar o'zi yangilanadi- Optimistik yangilash —
onMutate/onErrorbilan qaytarish useInfiniteQuery— cheksiz scrollfocusManager+AppState,onlineManager+NetInfo— mobilga moslash
Keyingi dars
33-dars: Zustand — global holat (savat, sozlamalar)