TanStack Query — Next.js bilan ishlash, server va client farqi

~20 daq · Next.js · Ma'lumot Olish

Muammodan boshlaylik

Siz Client Component da ma'lumot olmoqchisiz. Oddiy useEffect + fetch yozdingiz:

'use client'

export function ProductList() {
  const [products, setProducts] = useState([])
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(null)

  useEffect(() => {
    fetch('/api/products')
      .then(r => r.json())
      .then(setProducts)
      .catch(setError)
      .finally(() => setLoading(false))
  }, [])

  if (loading) return <Spinner />
  if (error)   return <p>Xato</p>
  return <ul>{products.map(...)}</ul>
}

Ishlaydi. Lekin muammolar bor:

  • Foydalanuvchi boshqa sahifaga o'tib qaytsa — ma'lumot qayta yuklanadi
  • Bir sahifada bir xil ma'lumot kerak bo'lsa — ikki marta so'rov ketadi
  • Xato bo'lganda qayta urinish yo'q
  • Ma'lumot eskirganini bilish yo'q

TanStack Query (avval React Query) bu muammolarni hal qiladi.


O'rnatish

npm install @tanstack/react-query

QueryClientProvider — butun ilova uchun bir marta o'rnatiladi:

// app/providers.tsx
'use client'

import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { useState } from 'react'

export function Providers({ children }: { children: React.ReactNode }) {
  const [queryClient] = useState(() => new QueryClient({
    defaultOptions: {
      queries: {
        staleTime: 60 * 1000,  // 1 daqiqa — ma'lumot "yangi" hisoblanadi
      },
    },
  }))

  return (
    <QueryClientProvider client={queryClient}>
      {children}
    </QueryClientProvider>
  )
}
// app/layout.tsx
import { Providers } from './providers'

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  )
}

useQuery — ma'lumot olish

'use client'

import { useQuery } from '@tanstack/react-query'

export function ProductList() {
  const { data, isLoading, error } = useQuery({
    queryKey: ['products'],
    queryFn: () => fetch('/api/products').then(r => r.json()),
  })

  if (isLoading) return <Spinner />
  if (error)     return <p>Xato: {error.message}</p>

  return (
    <ul>
      {data.map(product => (
        <li key={product.id}>{product.name}</li>
      ))}
    </ul>
  )
}

queryKey — kesh kaliti. ['products'] — bu so'rovning nomi. Bir xil queryKey bo'lsa — keshdan qaytariladi, qayta so'rov ketmaydi.

queryFn — ma'lumot oluvchi funksiya. Promise qaytarishi kerak.


queryKey — kesh kaliti

queryKey — TanStack Query ning eng muhim tushunchasi. U kesh kaliti sifatida ishlaydi.

// Har xil key — har xil kesh
useQuery({ queryKey: ['products'], ... })           // barcha mahsulotlar
useQuery({ queryKey: ['products', id], ... })       // bitta mahsulot
useQuery({ queryKey: ['products', { page: 2 }], ... }) // 2-sahifa

Bir xil queryKey bo'lsa — bir xil kesh. Sahifada 3 ta komponent ['products'] so'rasa — bitta so'rov ketadi.


useMutation — ma'lumot o'zgartirish

'use client'

import { useMutation, useQueryClient } from '@tanstack/react-query'

export function AddToCart({ productId }: { productId: number }) {
  const queryClient = useQueryClient()

  const mutation = useMutation({
    mutationFn: (productId: number) =>
      fetch('/api/cart', {
        method: 'POST',
        body: JSON.stringify({ productId }),
      }).then(r => r.json()),

    onSuccess: () => {
      // Savat ma'lumotini yangilash
      queryClient.invalidateQueries({ queryKey: ['cart'] })
    },
  })

  return (
    <button
      onClick={() => mutation.mutate(productId)}
      disabled={mutation.isPending}
    >
      {mutation.isPending ? 'Qo\'shilmoqda...' : 'Savatga qo\'shish'}
    </button>
  )
}

invalidateQueries — ['cart'] keshini "eskirgan" deb belgilaydi. Keyingi render da qayta yuklanadi.


Server Component bilan birga ishlatish

TanStack Query — Client Component uchun. Server Component da kerak emas — u to'g'ridan-to'g'ri async/await ishlatadi.

Lekin ikkalasini birga ishlatish mumkin — prefetching orqali. Server Component ma'lumotni oldindan yuklab, Client Component ga uzatadi:

// app/products/page.tsx — Server Component
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'
import { ProductList } from './ProductList'

export default async function ProductsPage() {
  const queryClient = new QueryClient()

  // Server da ma'lumotni oldindan yuklash
  await queryClient.prefetchQuery({
    queryKey: ['products'],
    queryFn: () => fetch('https://api.example.com/products').then(r => r.json()),
  })

  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <ProductList />
    </HydrationBoundary>
  )
}
// ProductList.tsx — Client Component
'use client'

import { useQuery } from '@tanstack/react-query'

export function ProductList() {
  const { data } = useQuery({
    queryKey: ['products'],
    queryFn: () => fetch('/api/products').then(r => r.json()),
  })
  // data — serverda yuklangan, darhol ko'rinadi. Loading yo'q!

  return <ul>{data?.map(p => <li key={p.id}>{p.name}</li>)}</ul>
}

Foydalanuvchi sahifani ochganda — ma'lumot allaqachon bor. Loading spinner ko'rinmaydi. Keyin foydalanuvchi o'zgartirishlar qilsa — Client da yangilanadi.


staleTime va gcTime

useQuery({
  queryKey: ['products'],
  queryFn: fetchProducts,
  staleTime: 5 * 60 * 1000,  // 5 daqiqa — ma'lumot "yangi"
  gcTime: 10 * 60 * 1000,    // 10 daqiqa — keshdan o'chiriladi
})

staleTime — ma'lumot qancha vaqt "yangi" hisoblanadi. Bu vaqt ichida qayta so'rov ketmaydi.

gcTime — ma'lumot keshda qancha vaqt saqlanadi. Komponent unmount bo'lgandan keyin.


Tez-tez uchraydigan xatolar

1. QueryClientProvider yo'q

Error: No QueryClient set, use QueryClientProvider to set one

Providers ni layout.tsx ga qo'shishni unutish — eng ko'p uchraydigan xato.

2. QueryClient ni komponent ichida yaratish

// ❌ — har render da yangi QueryClient, kesh yo'qoladi
'use client'
export function Providers({ children }) {
  const queryClient = new QueryClient()  // xato!
  return <QueryClientProvider client={queryClient}>...</QueryClientProvider>
}

// ✅ — useState bilan bir marta
const [queryClient] = useState(() => new QueryClient())

3. Server Component da useQuery ishlatish

// ❌ — Server Component da hook ishlamaydi
export default async function Page() {
  const { data } = useQuery(...)  // xato!
}

// ✅ — Server Component da to'g'ridan-to'g'ri fetch
export default async function Page() {
  const data = await fetch('/api/data').then(r => r.json())
}

Xulosa

TanStack Query — Client Component da ma'lumot olish uchun. Kesh, qayta urinish, deduplikatsiya — avtomatik.

useQuery      → ma'lumot olish, kesh, loading/error holatlari
useMutation   → ma'lumot o'zgartirish (POST, PUT, DELETE)
queryKey      → kesh kaliti, bir xil key = bir xil kesh
invalidateQueries → keshni yangilash
prefetchQuery → Server Component da oldindan yuklash

Server Component da TanStack Query kerak emas — async/await yetarli. Client Component da interaktiv ma'lumot uchun ishlatiladi.


Keyingi dars

22-dars: Server Action — nima, qanday ishlaydi, form bilan ishlatish

Forma yuborishda API endpoint yozmasdan, to'g'ridan-to'g'ri server funksiyasini chaqirish.