Ada banyak cara untuk consume API di React—mulai dari ribet manual dengan useEffect, sampai simpel dan modern dengan TanStack Query. Tujuannya tetap sama: menampilkan data ke user. Bedanya ada di proses, effort, dan pengalaman developer
useEffect + fetch jadi andalan, sampai akhirnya file komponen penuh dengan state loading, error, dan data. Seperti banyak hal di dunia development, ada banyak jalan menuju data. useEffect, kemudian custom hook, TanStack Query, hingga abstraction yang lebih tinggi menggunakan Refine.Sederhana: karena fetching data bukan hanya soal memanggil sebuah endpoint.
Saat aplikasi masih kecil, kita mungkin hanya membutuhkan sesuatu seperti:
useEffect(() => {
fetch("/api/users");
}, []);
Tidak terlihat sulit, bukan?
Masalahnya mulai muncul ketika aplikasi bertambah besar. Tiba-tiba ada banyak hal yang harus kita pikirkan:
Loading state yang harus konsisten di banyak komponen.
Error handling yang kadang berbeda-beda.
Caching supaya user nggak nunggu terus tiap pindah halaman.
Refetching saat data berubah di server.
Optimistic updates saat user melakukan aksi (POST/PUT/DELETE).
Kalau semua concern tersebut ditangani secara manual, kode bisa dengan cepat berubah menjadi spaghetti.
Di sinilah kita mulai membutuhkan pola yang lebih terstruktur.
Untuk melihat kenapa abstraction tersebut dibutuhkan, mari mulai dari pendekatan paling sederhana.
Ini adalah pola klasik yang mungkin pernah kamu tulis berkali-kali:
import { useEffect, useState } from "react";
function Users() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
setLoading(true);
fetch("https://jsonplaceholder.typicode.com/users")
.then(res => {
if (!res.ok) throw new Error("Network response was not ok");
return res.json();
})
.then(data => {
setUsers(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}Apa yang salah?
Secara fungsional, tidak ada yang benar-benar salah dengan kode tersebut.
Kamu butuh caching antar halaman.
Kamu butuh refetch otomatis saat window fokus.
Kamu punya banyak endpoint dan harus menulis ulang pola yang sama.
Intinya: boilerplate dan re-inventing the wheel.
Salah satu solusi pertama yang sering digunakan adalah memindahkan logic fetching ke custom hook.
import { useEffect, useState } from "react";
function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
setLoading(true);
setError(null);
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error("Failed to fetch data");
}
return response.json();
})
.then((result) => {
setData(result);
setLoading(false);
})
.catch((error) => {
setError(error);
setLoading(false);
});
}, [url]);
return {
data,
loading,
error,
};
}function Users() {
const { data, loading, error } = useFetch<User[]>(
"https://jsonplaceholder.typicode.com/users"
);
if (loading) {
return <p>Loading...</p>;
}
if (error) {
return <p>Error: {error.message}</p>;
}
return (
<ul>
{data?.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
Jauh lebih rapi.
Setidaknya sekarang component tidak lagi mengetahui bagaimana proses fetching dilakukan.
Tapi sebenarnya kita baru memindahkan masalah, bukan benar-benar menyelesaikannya.
Custom hook tersebut masih harus menangani banyak hal jika aplikasi semakin kompleks:
Kalau kita mulai membangun semua fitur tersebut sendiri, custom hook yang awalnya sederhana bisa berubah menjadi sebuah mini framework.
Dan kalau masalahnya sudah seperti itu, mungkin kita memang tidak perlu membangun semuanya sendiri.
Di sinilah TanStack Query mulai menarik.
TanStack Query bukan sekadar library untuk melakukan fetch. Ia dirancang untuk mengelola server state.
Perbedaannya cukup penting.
useState dan useEffect adalah primitive untuk membangun behavior di React.
Sementara TanStack Query memberikan abstraction untuk masalah yang memang sering muncul ketika aplikasi berinteraksi dengan server.
Contohnya:
Caching otomatis antar komponen.
Background refetch saat window fokus atau reconnect.
Retries otomatis saat request gagal.
Pagination dan infinite queries built-in.
Mutations dengan optimistic updates dan invalidation.
import { useQuery } from "@tanstack/react-query";
function Users() {
const { data, isLoading, error } = useQuery({
queryKey: ["users"],
queryFn: () => fetch("https://jsonplaceholder.typicode.com/users").then(r => r.json()),
});
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error</p>;
return <ul>{data.map((u: any) => <li key={u.id}>{u.name}</li>)}</ul>;
}Perhatikan apa yang sudah hilang.
Kita tidak lagi membutuhkan useState untuk menyimpan data.
Kita juga tidak perlu menulis useEffect hanya untuk menjalankan request.
State seperti loading dan error juga sudah dikelola oleh TanStack Query.
Yang lebih penting, component sekarang tidak perlu memikirkan bagaimana cara membuat caching atau refetching dari awal.
Tetapi ada satu hal yang perlu diperhatikan.
TanStack Query menyelesaikan masalah server-state management, bukan berarti seluruh arsitektur data aplikasi otomatis selesai.
Ketika aplikasi semakin besar, kita mungkin memiliki banyak resource seperti users, products, orders, posts, dan sebagainya.
Di sinilah abstraction yang lebih tinggi mulai terasa menarik.
useListalau TanStack Query membuat kita tidak perlu mengelola server state secara manual, Refine mencoba membawa abstraction tersebut lebih jauh lagi.
Refine menyediakan pola untuk membangun aplikasi yang banyak berinteraksi dengan resource atau API.
Salah satu hook yang menarik adalah useList. Contoh sederhananya:
import { useList } from "@refinedev/core";
export const UserList = () => {
const { data, isLoading, error } = useList({
resource: "users",
});
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error</p>;
return (
<ul>
{data?.data.map((user: any) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
};Bandingkan dengan contoh TanStack Query sebelumnya.
Pada TanStack Query kita masih menentukan: queryKey & queryFn
Sementara dengan useList, kita cukup menentukan resource yang ingin digunakan.
Tapi tentu saja Refine tidak menebak endpoint API secara ajaib.
Di belakang abstraction tersebut terdapat konsep seperti resource dan data provider yang menentukan bagaimana Refine berkomunikasi dengan backend.
Karena konfigurasi tersebut sudah disiapkan, component tidak perlu lagi mendefinisikan queryFn atau mengelola query key secara manual.
Jadi abstraction-nya kurang lebih seperti ini:
useEffect + fetch
↓
Custom Hook
↓
TanStack Query
↓
RefineSemakin ke bawah, semakin tinggi level abstraction yang kita gunakan.
Namun, semakin tinggi abstraction bukan berarti selalu semakin baik.
Semua kembali pada kebutuhan aplikasi.
Setelah melihat beberapa pendekatan di atas, mungkin muncul pertanyaan:
“Berarti sekarang jangan pernah pakai
useEffectuntuk fetching?”
Tidak juga.
useEffect tetap merupakan bagian penting dari React dan sangat berguna untuk berbagai jenis side effect. Misalnya:
Yang perlu kita hindari adalah menggunakan useEffect sebagai solusi default untuk setiap kebutuhan server state tanpa mempertimbangkan kompleksitasnya.
Kalau aplikasi membutuhkan caching, refetching, mutation, invalidation, pagination, atau sinkronisasi data yang lebih kompleks, library seperti TanStack Query biasanya akan membuat kode jauh lebih mudah dipelihara.
Kalau harus diringkas, saya biasanya melihatnya seperti ini:
useEffect untuk side effect yang memang membutuhkan lifecycle React.retry dan staleTime di QueryClient agar behavior aplikasi tetap konsisten.Kita semua punya tujuan yang sama: menyajikan data dengan cepat, andal, dan mudah dipelihara. useEffect mengajarkan dasar-dasar dan cocok untuk kasus sederhana. TanStack Query menawarkan jalan tol: lebih sedikit boilerplate, fitur built-in untuk masalah nyata, dan pengalaman developer yang lebih baik. Pilih jalannya sesuai kebutuhan, tapi kalau proyekmu mulai tumbuh, beralih ke TanStack Query biasanya keputusan yang bijak.
No members yet — be the first!
React baru saja menjadi fenomena global, dan setiap orang ingin belajar. Masalahnya? Setup project React
94 views
Sarkss Dulu
23 views
Memahami Big O Notation bukan sekadar teori matematis kampus, melainkan kunci fundamental dalam mengoptimalkan performa aplikasi. Intip perbandingan nyata antara Bubble Sort dan Merge Sort untuk memahami trade-off antara kecepatan dan penggunaan memori.
16 views