Optimasi Data Fetching di Frontend: Menggali Lebih Dalam React Query (TanStack Query) dan SWR
Pernahkah Anda merasa frustrasi dengan kerumitan mengelola data di aplikasi web? Mulai dari menampilkan status loading, menangani error, memastikan data selalu up-to-date, hingga mencegah request yang berlebihan. Jika ya, Anda tidak sendirian. Data fetching adalah salah satu tantangan terbesar dalam pengembangan frontend modern.
Di artikel ini, kita akan menyelami dunia React Query (sekarang TanStack Query) dan SWR, dua library revolusioner yang mengubah cara kita berpikir tentang data fetching. Mereka bukan hanya sekadar alat untuk mengambil data, tapi juga solusi komprehensif untuk caching, refetching, synchronization, dan state management data server Anda. Mari kita mulai!
1. Pendahuluan: Mengapa Data Fetching Butuh Solusi Khusus?
Dalam aplikasi web modern, data adalah jantungnya. Hampir setiap interaksi melibatkan pengambilan data dari backend, menampilkannya, dan mungkin memodifikasinya. Secara tradisional, kita sering menggunakan useEffect dan useState di React untuk melakukan ini:
import React, { useState, useEffect } from 'react';
function MyComponent() {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
setIsLoading(true);
const response = await fetch('/api/posts');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (err) {
setError(err);
} finally {
setIsLoading(false);
}
};
fetchData();
}, []); // Dependensi kosong, hanya fetch sekali saat mount
if (isLoading) return <p>Memuat data...</p>;
if (error) return <p>Terjadi kesalahan: {error.message}</p>;
return (
<div>
{/* Tampilkan data */}
</div>
);
}
Kode di atas terlihat sederhana, bukan? Tapi ini baru permulaan. Bayangkan jika Anda perlu:
- ✅ Refetch data saat pengguna kembali ke halaman.
- ✅ Invalidasi cache setelah melakukan update data.
- ✅ Mencegah duplicate requests saat komponen yang sama me-mount berulang kali.
- ✅ Mengimplementasikan retry otomatis saat jaringan tidak stabil.
- ✅ Optimistic updates untuk UX yang lebih responsif.
- ✅ Prefetch data untuk navigasi yang super cepat.
Semua ini akan menambah kompleksitas yang signifikan pada boilerplate kode Anda. Di sinilah React Query dan SWR hadir sebagai penyelamat. Mereka menyediakan abstraksi yang kuat untuk mengelola server state, memungkinkan Anda fokus pada logika bisnis, bukan pada detail data fetching yang melelahkan.
2. Memahami Perbedaan Fundamental: UI State vs. Server State
Sebelum kita melangkah lebih jauh, penting untuk memahami perbedaan antara UI State dan Server State:
- UI State (Client State): Ini adalah data yang sepenuhnya Anda kontrol di frontend. Contohnya: apakah modal terbuka, nilai input form, tema aplikasi (gelap/terang). Perubahan pada UI state bersifat synchronous dan segera terlihat.
- Server State: Ini adalah data yang disimpan di backend dan diambil oleh frontend. Contohnya: daftar postingan blog, profil pengguna, daftar produk.
- ❌ Tidak dikontrol penuh oleh frontend: Data bisa berubah di luar aplikasi Anda (misalnya, pengguna lain mengedit data).
- ❌ Membutuhkan fetching dan synchronization: Anda perlu mengambilnya dan memastikan versi di frontend sesuai dengan backend.
- ❌ Bisa “stale” (kadaluarsa): Data yang Anda ambil beberapa waktu lalu mungkin tidak lagi akurat.
- ❌ Membutuhkan caching dan refetching: Untuk performa dan konsistensi.
React Query dan SWR dirancang khusus untuk mengelola kompleksitas Server State ini. Mereka membawa pola-pola terbaik dari data fetching ke frontend Anda.
3. React Query (TanStack Query): Toolkit Lengkap untuk Server State
React Query, yang kini menjadi bagian dari ekosistem TanStack Query, adalah library yang sangat powerful dan fleksibel. Ia menyediakan “hooks” yang intuitif untuk mengelola server state Anda dengan cara yang efisien dan declarative.
📌 Konsep Inti React Query:
- Caching Otomatis: Data yang sudah diambil akan disimpan di cache.
- Background Refetching: Saat data di cache dianggap “stale” (kadaluarsa), React Query akan mengambil data baru di background tanpa memblokir UI.
- Deduplication: Mencegah request yang sama dikirim berulang kali.
- Retry: Otomatis mencoba ulang request yang gagal.
- Invalidation: Cara mudah untuk memberitahu React Query bahwa data di cache sudah tidak valid dan perlu diambil ulang.
Contoh Penggunaan useQuery
Mari kita ubah contoh MyComponent sebelumnya menggunakan React Query:
import React from 'react';
import { useQuery, QueryClient, QueryClientProvider } from '@tanstack/react-query'; // Atau '@tanstack/react-query' untuk versi terbaru
// Buat instance QueryClient
const queryClient = new QueryClient();
// Fungsi untuk fetching data
const fetchPosts = async () => {
const response = await fetch('/api/posts');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
};
function MyComponent() {
const { data, isLoading, isError, error } = useQuery({
queryKey: ['posts'], // Kunci unik untuk cache data ini
queryFn: fetchPosts, // Fungsi yang akan melakukan fetching
staleTime: 5 * 60 * 1000, // Data dianggap fresh selama 5 menit
cacheTime: 10 * 60 * 1000, // Data akan tetap di cache selama 10 menit setelah tidak digunakan
});
if (isLoading) return <p>Memuat data...</p>;
if (isError) return <p>Terjadi kesalahan: {error.message}</p>;
return (
<div>
<h1>Daftar Postingan</h1>
{data.map(post => (
<div key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</div>
))}
</div>
);
}
// Aplikasi Anda harus dibungkus dengan QueryClientProvider
function App() {
return (
<QueryClientProvider client={queryClient}>
<MyComponent />
</QueryClientProvider>
);
}
export default App;
💡 Penjelasan Konsep:
QueryClientProvider: Ini mirip denganContext.Providerdi React. Ia menyediakanQueryClientke semua komponen di bawahnya.queryKey: Ini adalah array unik yang digunakan React Query untuk mengidentifikasi dan menyimpan data di cache. JikaqueryKeyberubah, React Query akan menganggapnya sebagai data yang berbeda dan akan fetch ulang.queryFn: Ini adalah fungsi asynchronous yang bertugas mengambil data dari backend.staleTime: Durasi (dalam milidetik) di mana data di cache dianggap “fresh”. Selama data fresh,useQueryakan langsung mengembalikan data tersebut tanpa request baru. SetelahstaleTimeterlampaui, data menjadi “stale”, danuseQueryakan memicu background refetch saat komponen me-mount atau window focus. Default-nya0, artinya data langsung stale.cacheTime: Durasi (dalam milidetik) di mana data akan tetap berada di cache setelah tidak lagi digunakan oleh komponen manapun. SetelahcacheTimeterlampaui, data akan dihapus dari cache. Default-nya5 * 60 * 1000(5 menit).
✅ Manfaat langsung:
- Loading, error, dan data state sudah diurus.
- Data otomatis di-cache.
- Saat Anda kembali ke halaman ini (misal dari halaman lain), jika data sudah stale, ia akan di-refetch di background sambil menampilkan data lama, memberikan UX yang mulus.
- Jika ada komponen lain yang menggunakan
queryKey: ['posts'], mereka akan berbagi data yang sama dari cache.
4. SWR: Simplicity with Stale-While-Revalidate
SWR (Stale-While-Revalidate) adalah library lain yang populer, dikembangkan oleh tim Vercel (pembuat Next.js). Namanya berasal dari strategi HTTP caching stale-while-revalidate.
🎯 Konsep Inti SWR:
- Tampilkan data stale segera: SWR akan segera menampilkan data yang ada di cache (jika ada).
- Revalidate di background: Secara bersamaan, SWR akan mengirim request baru untuk mengambil data terbaru dari backend.
- Update UI jika data berubah: Setelah data baru diterima, SWR akan membandingkannya dengan data stale dan meng-update UI jika ada perubahan.
Ini memberikan pengalaman pengguna yang sangat cepat karena UI tidak perlu menunggu data terbaru untuk ditampilkan.
Contoh Penggunaan useSWR
Mari kita adaptasi MyComponent menggunakan SWR:
import React from 'react';
import useSWR, { SWRConfig } from 'swr';
// Fungsi fetcher untuk SWR
const fetcher = async (url) => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
};
function MyComponent() {
const { data, error, isLoading } = useSWR('/api/posts', fetcher);
if (isLoading) return <p>Memuat data...</p>;
if (error) return <p>Terjadi kesalahan: {error.message}</p>;
return (
<div>
<h1>Daftar Postingan</h1>
{data.map(post => (
<div key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</div>
))}
</div>
);
}
// Aplikasi Anda bisa dibungkus dengan SWRConfig untuk konfigurasi global
function App() {
return (
<SWRConfig value={{
refreshInterval: 3000, // Otomatis refetch setiap 3 detik
revalidateOnFocus: true, // Refetch saat window focus
}}>
<MyComponent />
</SWRConfig>
);
}
export default App;
💡 Penjelasan Konsep:
useSWR(key, fetcher):key: String unik yang juga berfungsi sebagai URL endpoint. Ini yang digunakan SWR untuk caching.fetcher: Fungsi asynchronous yang menerimakeydan mengembalikan data.
SWRConfig: Mirip denganQueryClientProvider, ini memungkinkan Anda mengatur konfigurasi global untuk semuauseSWRdi aplikasi Anda.
✅ Manfaat langsung:
- Sangat mudah diatur dan digunakan.
- Memberikan UX yang cepat dengan menampilkan data stale terlebih dahulu.
- Otomatis refetch saat window focus atau jika ada interval refresh yang diatur.
5. React Query vs. SWR: Kapan Memilih yang Mana?
Meskipun keduanya bertujuan untuk memecahkan masalah yang sama, ada beberapa perbedaan yang bisa menjadi pertimbangan:
| Fitur/Aspek | React Query (TanStack Query) | SWR |
|---|---|---|
| Fokus Utama | Toolkit lengkap untuk server state, lebih banyak fitur. | Simplicity, pola stale-while-revalidate. |
| API | Lebih opinionated, banyak opsi konfigurasi per query. | Sangat minimalis, mudah dipelajari. |
| Optimistic Updates | Sangat powerful dan terintegrasi dengan baik. | Membutuhkan implementasi manual yang sedikit lebih rumit. |
| Prefetching | Mudah dilakukan dengan queryClient.prefetchQuery(). | Membutuhkan trik atau workaround. |
Mutations (useMutation) | API yang kuat untuk POST/PUT/DELETE, dengan optimistic updates. | Tidak ada API khusus, perlu penanganan manual. |
| DevTools | DevTools yang sangat lengkap dan informatif. | DevTools lebih dasar, namun tetap membantu. |
| Ukuran Bundle | Sedikit lebih besar karena fitur yang lebih banyak. | Lebih ringan. |
| Kapan memilih | Proyek skala besar, butuh kontrol penuh atas cache, optimistic updates yang kompleks, mutations. | Proyek kecil hingga menengah, prioritas pada kesederhanaan, fast UX dengan stale-while-revalidate. |
💡 Kesimpulan umum: Jika Anda mencari solusi yang paling lengkap dan fleksibel untuk manajemen server state dengan fitur optimistic update dan mutation yang kuat, React Query adalah pilihan yang sangat baik. Jika Anda mengutamakan kesederhanaan, ukuran bundle yang kecil, dan pola stale-while-revalidate yang efektif, SWR adalah pilihan yang solid.
6. Best Practices dan Tips Praktis
Menggunakan React Query atau SWR saja sudah memberikan peningkatan signifikan, tetapi ada beberapa praktik terbaik untuk memaksimalkan potensi mereka:
📌 1. Invalidasi Cache yang Tepat
Setelah melakukan operasi POST, PUT, atau DELETE, data di cache Anda kemungkinan besar sudah tidak valid. Anda perlu memberi tahu library untuk me-refetch data terkait.
Dengan React Query (useMutation):
import { useMutation, useQueryClient } from '@tanstack/react-query';
function AddPostForm() {
const queryClient = useQueryClient();
const addPost = useMutation({
mutationFn: (newPost) => fetch('/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newPost),
}).then(res => res.json()),
onSuccess: () => {
// ✅ Invalidasi cache 'posts' setelah post baru berhasil ditambahkan
queryClient.invalidateQueries({ queryKey: ['posts'] });
},
});
// ...
}
Dengan SWR (mutate):
import useSWR, { mutate } from 'swr';
function AddPostForm() {
// ... (logika form)
const handleSubmit = async (newPost) => {
await fetch('/api/posts', { /* ... */ });
// ✅ Invalidasi cache '/api/posts'
mutate('/api/posts');
};
// ...
}
💡 2. Optimistic Updates
Untuk pengalaman pengguna yang sangat responsif, gunakan optimistic updates. Ini berarti Anda meng-update UI seolah-olah request ke backend sudah berhasil, lalu me-revert jika request gagal.
React Query memiliki API yang kuat untuk ini di useMutation:
const addPost = useMutation({
mutationFn: (newPost) => fetch('/api/posts', { /* ... */ }),
onMutate: async (newPost) => {
// ✅ Batalkan refetch yang sedang berjalan untuk menghindari race condition
await queryClient.cancelQueries({ queryKey: ['posts'] });
// ✅ Simpan data cache sebelumnya
const previousPosts = queryClient.getQueryData(['posts']);
// ✅ Update cache secara optimistic
queryClient.setQueryData(['posts'], (old) => [...old, { id: Date.now(), ...newPost }]);
return { previousPosts }; // Kembalikan context untuk onError
},
onError: (err, newPost, context) => {
// ❌ Jika gagal, kembalikan cache ke kondisi sebelumnya
queryClient.setQueryData(['posts'], context.previousPosts);
},
onSettled: () => {
// ✅ Pastikan data terbaru diambil setelah mutation selesai
queryClient.invalidateQueries({ queryKey: ['posts'] });
},
});
✅ 3. Manfaatkan DevTools
Kedua library ini menyediakan DevTools yang sangat membantu untuk memvisualisasikan cache, queries, dan mutations Anda. Jangan ragu untuk menggunakannya selama pengembangan untuk debugging dan optimasi.
⚠️ 4. Hindari useEffect untuk Data Fetching Utama
Setelah Anda mengadopsi React Query atau SWR, sebisa mungkin hindari pola useEffect + useState untuk data fetching utama. Biarkan library ini yang mengelola server state Anda. Gunakan useEffect hanya untuk side effects yang tidak berhubungan dengan data fetching atau synchronization server.
Kesimpulan
Mengelola data di aplikasi frontend modern adalah tugas yang kompleks. Dengan React Query (TanStack Query) dan SWR, Anda dapat mengubah boilerplate kode yang membosankan dan rentan kesalahan menjadi logic data fetching yang bersih, efisien, dan tangguh. Mereka mengurus caching, refetching, synchronization, error handling, dan banyak lagi, memungkinkan Anda fokus pada fitur-fitur yang benar-benar penting bagi pengguna Anda.
Memilih antara keduanya tergantung pada kebutuhan proyek Anda. Jika Anda menginginkan solusi yang sangat lengkap dan fleksibel, React Query adalah pilihan yang tepat. Jika Anda mencari kesederhanaan dan kecepatan dengan pola stale-while-revalidate, SWR akan melayani Anda dengan baik. Apapun pilihan Anda, pastikan untuk mengadopsi praktik terbaik untuk mendapatkan hasil maksimal dari library ini dan memberikan pengalaman pengguna yang luar biasa. Selamat mencoba!
🔗 Baca Juga
- Mengelola Data GraphQL di Frontend: Memilih dan Menggunakan Apollo Client untuk Aplikasi Modern
- Zustand: State Management Simpel dan Kuat untuk Aplikasi React Modern
- Membangun User Experience yang Responsif: Mengimplementasikan Optimistic UI
- Mengoptimalkan Monorepo Anda: Panduan Praktis dengan Nx dan Turborepo