Episode ini mengajarkan konsumsi REST dan GraphQL API, fetch policies dengan retries dan retry delays, centralized error handling lengkap dengan fallback UI, serta preloading data dan optimistic updates untuk pengalaman pengguna yang lebih baik.

Integrasi API yang buruk terasa di tangan pengguna: spinner berputar tanpa henti, request gagal tanpa penjelasan, atau daftar yang tidak sinkron setelah aksi. Episode 13 membuat integrasi kalian tangguh.
Kita mulai dari konsumsi REST dan GraphQL, lalu membahas fetch policies dengan retries dan retry delays, centralized error handling dengan fallback UI yang baik, dan ditutup dua pola UX canggih: preloading data dan optimistic updates.
Pisahkan logika API dari komponen dengan membuat module sendiri. Ini memudahkan pemakaian ulang dan pengujian:
// src/api/users.js
export async function getUsers() {
const res = await fetch("https://api.example.com/users")
if (!res.ok) throw new Error(`HTTP ${res.status}`)
return res.json()
}
// komponen memanggil
import { getUsers } from "../api/users.js"
const users = await getUsers()export async function getUsers() mengisolasi detail endpoint ke satu tempat. Komponen hanya tahu fungsi ini mengembalikan array users atau melempar error.
Untuk GraphQL, query dibangun dengan bahasa query dan dikirim via HTTP POST. Memakai library seperti urql atau @apollo/client:
import { useQuery, gql } from "urql"
const USERS_QUERY = gql`
query {
users {
id
name
}
}
`
function DaftarUser() {
const [result] = useQuery({ query: USERS_QUERY })
if (result.fetching) return <p>Memuat...</p>
if (result.error) return <p>Error: {result.error.message}</p>
return <ul>{result.data.users.map((u) => <li key={u.id}>{u.name}</li>)}</ul>
}useQuery({ query: USERS_QUERY }) menjalankan query GraphQL dan memberi fetching, error, dan data. Keunggulan GraphQL: klien hanya mengambil field yang benar-benar dibutuhkan.
Jaringan tidak bisa diandalkan. Retries dengan jeda eksponensial membuat request sementara gagal menjadi sukses otomatis:
import { useQuery } from "@tanstack/react-query"
function DaftarUser() {
const { data, error, refetch } = useQuery({
queryKey: ["users"],
queryFn: getUsers,
retry: 3,
retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30000),
})
if (error) return (
<div>
<p>Gagal memuat data</p>
<button onClick={() => refetch()}>Coba lagi</button>
</div>
)
return <ul>{data?.map((u) => <li key={u.id}>{u.name}</li>)}</ul>
}retry: 3 mencoba ulang hingga tiga kali, dan retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30000) memakai jeda eksponensial dengan batas 30 detik — mencegah membebani server saat jaringan turun.
Tangan dua jenis error secara berbeda:
function Fallback({ pesan, onRetry }) {
return (
<div role="alert">
<p>Maaf, terjadi kesalahan: {pesan}</p>
<button onClick={onRetry}>Muat ulang</button>
</div>
)
}<div role="alert">{:javascript}</div> mengumumkan error ke screen reader. Fallback yang baik memberi tahu apa yang salah dan memberi jalan keluar — bukan sekadar layar kosong.
Centralized error handling berarti semua request melewati satu titik yang konsisten menangani kegagalan — misalnya axios interceptor yang menangkap status 401 untuk logout otomatis, atau hook useApi yang membungkus useQuery dengan konfigurasi error seragam. Satu perubahan kebijakan cukup di satu tempat.
Preloading mengambil data lebih awal, misalnya saat pengguna mengarahkan kursor ke tombol, sehingga halaman target terasa instan:
import { useQueryClient } from "@tanstack/react-query"
function Menu() {
const queryClient = useQueryClient()
const prefetchUsers = () => {
queryClient.prefetchQuery({ queryKey: ["users"], queryFn: getUsers })
}
return <Link to="/users" onMouseEnter={prefetchUsers}>Pengguna</Link>
}queryClient.prefetchQuery({ queryKey: ["users"], queryFn: getUsers }) mengambil data di background saat kursor masuk link. Ketika halaman terbuka, data sudah ada di cache — tanpa spinner.
Untuk aksi cepat seperti suka atau hapus, optimistic update mengubah UI dulu lalu memperbarui server di belakang:
const { mutate } = useMutation({
mutationFn: hapusUser,
onMutate: async (id) => {
await queryClient.cancelQueries({ queryKey: ["users"] })
queryClient.setQueryData(["users"], (old) =>
old.filter((u) => u.id !== id)
)
},
})onMutate langsung memperbarui cache sebelum respons server datang. Jika server gagal, gunakan onError untuk mengembalikan cache ke keadaan semula — dan tampilkan pesan singkat bahwa sinkronisasi gagal.
npm install urql graphqlPerintah npm install urql graphql memasang klien GraphQL. Coba ganti salah satu fetching REST kalian dengan query GraphQL untuk merasakan perbedaannya.
Episode 13 membuat integrasi API kalian tangguh: layer API untuk REST dan GraphQL, retries dengan jeda eksponensial, centralized error handling dengan fallback UI, lalu preloading dan optimistic updates untuk UX yang mulus.
Inti yang harus dibawa pulang:
Di episode 14 selanjutnya kita akan membahas networking performance — HTTP caching dan stale-while-revalidate, code splitting dan bundle analysis, prefetching data dan assets, serta monitoring performa jaringan. Aplikasi kalian tidak hanya berfungsi, tapi juga cepat.