# 🎯 Best Practice Menggunakan @tanstack/react-query

**Meningkatkan Kualitas Data Fetching di Aplikasi Modern**

Di ekosistem React modern, mengelola data server—fetching, caching, synchronizing, dan state management—sering menjadi tantangan. Banyak developer masih memakai pendekatan manual seperti `useEffect + fetch`, yang rawan menyebabkan:

* Request ganda yang tidak perlu
    
* Loading state yang berantakan
    
* Race condition
    
* Cache yang tidak sinkron ketika data berubah
    
* Kompleksitas logic untuk polling, retry, dan refetch
    

React Query hadir sebagai solusi *battle-tested* untuk membuat data fetching menjadi **declarative, predictable, dan scalable**.

Artikel ini membahas praktik terbaik (**best practice**) agar penggunaan React Query di proyek Anda rapi, efisien, dan maintainable.

---

# 🧠 1. Pahami Filosofi React Query: “Server State ≠ Client State”

React Query fokus pada **server state**, bukan data UI.

* Server state **berubah di luar kontrol aplikasi** (misal: ada user lain update data).
    
* Server state **tidak boleh kamu rawat manual dengan useState/useEffect**.
    

React Query memberi:

* **Caching otomatis**
    
* **Stale-while-revalidate**
    
* **Background refresh**
    
* **Retry otomatis**
    
* **Refetch otomatis saat window fokus**
    
* **Garbage collection data lama**
    
* **Optimistic update**
    

Filosofi dasarnya:

> Jangan bawa urusan server ke state management manual. Delegasikan ke React Query.

---

# 🔑 2. Gunakan `queryKey` Berbentuk Array (Best Practice Mutlak)

Kenapa harus array?

✔ **Struktur terprediksi**  
✔ **Mendukung nested keys**  
✔ **Mudah di-compute**  
✔ **Stabil untuk caching**  
✔ **Membedakan query berdasarkan parameter**

Contoh:

```ts
useQuery({
  queryKey: ['users'],
  queryFn: getUsers
})

useQuery({
  queryKey: ['users', userId],
  queryFn: () => getUserById(userId)
})
```

Array membuat React Query dapat:

* Memahami konteks query
    
* Meng-cache data berdasarkan struktur key
    
* Melakukan invalidasi granular
    

---

# 🔁 3. Untuk POST/PUT/DELETE: Gunakan `useMutation` (Bukan `useQuery`)

**Best practice yang sangat penting:**

| Jenis Operasi | Hook yang dipakai | Alasan |
| --- | --- | --- |
| GET (fetch) | `useQuery` | Data bisa dicache |
| POST/PUT/DELETE (mutate) | `useMutation` | Tidak dicache, sifatnya *side effect* |

Kenapa tidak memakai `useQuery` untuk POST?  
Karena POST tidak bersifat *idempotent*, dan React Query bisa:

* memicu refetch otomatis
    
* menyimpan hasil POST ke cache secara tidak wajar
    
* menganggap POST sebagai “data state”
    

`useMutation` mendesain operasi mutate sebagai *action*, bukan *state*.

Best practice:

```ts
const mutation = useMutation({
  mutationFn: updateUser,
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['users'] }) 
  }
})
```

---

# 🔄 4. Saat Data di Update dari Server (Tanpa Invalidasi Query)

Kadang data berubah di server, tapi Anda tidak bisa memanggil `invalidateQueries`. Misal:

* Data berubah karena user lain
    
* Data berubah oleh sistem background
    
* Data dikirim via WebSocket
    
* Server melakukan push update (SSE)
    

Best practice React Query:

### **Gunakan salah satu strategi:**

#### ✔ 1. `refetchInterval`

Polling otomatis:

```ts
useQuery({
  queryKey: ['notifications'],
  queryFn: getNotifications,
  refetchInterval: 5000 // setiap 5 detik
})
```

> ❓ Jika halaman tidak terbuka apakah refetch tetap berjalan?  
> **Tidak**. React Query *pause* saat tab tidak aktif menggunakan `document.visibilityState`.

#### ✔ 2. Gunakan WebSocket + `queryClient.setQueryData`

Saat server push data:

```ts
socket.on('new_message', message => {
  queryClient.setQueryData(['messages'], old => [...old, message])
})
```

#### ✔ 3. Gunakan SSE (Server-Sent Events)

Integrasi mudah seperti WebSocket.

#### ✔ 4. Manual merge data dengan `setQueryData`

Jika update datang dari sumber lain.

---

# ⚙️ 5. Gunakan `select` untuk Transformasi Data di Query

Daripada transform data di component (boros render), lakukan di query:

```ts
useQuery({
  queryKey: ['products'],
  queryFn: getProducts,
  select: data => data.map(p => ({ ...p, price: p.price / 100 }))
})
```

**Keuntungan:**

* Component tetap ringan
    
* Transformasi terjadi sekali
    
* Konsisten
    

---

# 🧹 6. Atur Stale Time & Cache Time dengan Bijak

Default React Query sangat agresif karena diasumsikan untuk data yang sering berubah.

Best practice:

### ✔ Untuk data jarang berubah (misal countries):

```ts
staleTime: Infinity
```

### ✔ Untuk data sedang (misal: users list)

```ts
staleTime: 1000 * 60 * 5 // 5 menit
```

### ✔ Untuk detail page:

```ts
staleTime: 1000 * 30 // 30 detik
```

---

# ⚡ 7. Optimistic Update yang Aman

Untuk UI yang responsif:

```ts
useMutation({
  mutationFn: updateTodo,
  onMutate: async newTodo => {
    await queryClient.cancelQueries(['todos'])

    const prev = queryClient.getQueryData(['todos'])

    queryClient.setQueryData(['todos'], old =>
      old.map(t => t.id === newTodo.id ? newTodo : t)
    )

    return { prev }
  },
  onError: (_err, _newTodo, ctx) => {
    queryClient.setQueryData(['todos'], ctx.prev)
  },
  onSettled: () => {
    queryClient.invalidateQueries(['todos'])
  }
})
```

---

# 🧱 8. Centralize Query Key — Jangan Hardcode

Buat file:

### `/src/lib/react-query-key.ts`

```ts
export const queryKeys = {
  users: () => ['users'] as const,
  user: (id: string) => ['users', id] as const,
  products: () => ['products'] as const,
}
```

Ini mencegah typo dan membuat invalidasi lebih aman.

---

# 🧭 9. Hindari Overfetching (Kesalahan Umum)

Jangan gunakan `refetchInterval` untuk semua data.

Pertanyaan utama:

* Apakah data sering berubah?
    
* Apakah perubahan harus tampil real-time?
    
* Apakah polling terlalu mahal?
    

Jika tidak perlu → hilangkan polling.

---

# 🧰 10. Set Up Global di Query Client (Best Practice Paling Penting)

```ts
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: 2,
      refetchOnWindowFocus: true,
      refetchOnReconnect: true,
      staleTime: 1000 * 60, // 1 menit
    }
  }
})
```

Global config ini memastikan aplikasi tetap efisien tanpa pengaturan manual di setiap query.

---

# 🏆 Penutup: React Query Membuat Aplikasi Lebih Stabil, Cepat, dan Bersih

Menggunakan React Query dengan best practice:

* menghilangkan boilerplate
    
* mencegah bug server state
    
* membuat aplikasi scalable
    
* cache bekerja otomatis
    
* UI terasa lebih cepat dan mulus
    
* logic lebih bersih dan terstruktur
    
* memudahkan debugging & performance tracing
    

React Query bukan sekadar “data fetching library”.

Ia adalah **server state ecosystem** yang menyelesaikan masalah mendasar yang tidak bisa diselesaikan oleh React state biasa.

Jika digunakan dengan benar—dengan `queryKey` yang konsisten, strategi pada mutation, invalidasi yang tepat, dan manajemen stale time—React Query menjadi tulang punggung arsitektur front-end modern.
