Episode ini mengupas query keys sebagai identitas cache: array hierarkis, key dengan objek filter, dan cara query key menghidupkan deduplication serta invalidasi. Kalian juga memahami structural sharing dan cara menghindari double fetch.

Di episode 4 kalian memakai queryKey sebagai kotak ajaib. Episode 5 membuka kotak itu: query key sebenarnya adalah identitas cache. Segala sesuatu di TanStack Query — penyimpanan, deduplication, invalidasi — berpijak pada query key. Kalau kalian salah memahami key, fitur-fitur canggih berikutnya akan terasa seperti keajaiban yang tidak bisa diprediksi.
Episode 5 membahas struktur query key yang baik, cara kerja cache dan deduplication, serta structural sharing yang menjaga performa.
Query key adalah array yang bisa berisi string, number, objek, atau kombinasi semuanya. Array ini di-hash secara deterministik menjadi identitas sebuah entri cache:
["todos"]
["todos", 5]
["todos", "detail", 5]
["todos", { status: "done" }]
["users", 42, { sortBy: "name" }]["todos", 5] mengidentifikasi cache yang berbeda dari ["todos", 6]. Objek pun dibedakan: ["todos", { status: "done" }] adalah entri yang berbeda dari ["todos", { status: "pending" }]. Selama serialisasi array-nya sama, cache-nya sama.
Key disusun hierarkis — element pertama adalah domain, element kedua hingga seterusnya adalah identifikasi lebih spesifik. Hierarki ini bukan sekadar gaya: dia memungkinkan invalidasi prefix. Menyebut ["todos"] bisa membidik seluruh key yang diawali dengan ["todos", ...].
useQuery({ queryKey: ["todos"], queryFn: fetchTodos })
useQuery({ queryKey: ["todos", id], queryFn: fetchTodoById })
useQuery({
queryKey: ["todos", id, "comments"],
queryFn: fetchTodoComments,
})Semua key di atas berawal dari prefix ["todos"]. Dengan queryClient.invalidateQueries({ queryKey: ["todos"] }), ketiganya bisa di-refetch sekaligus — pola ini akan menjadi tulang punggung sinkronisasi di episode 6.
queryFn.["todos", 5] dan [5, "todos"] adalah cache berbeda.Ketika dua komponen memakai query key yang identik, TanStack Query mendeteksi bahwa entri cache sama dan menggabungkan keduanya menjadi satu fetch. Ini berbeda dari pola manual di mana setiap komponen yang di-mount akan fetch sendiri-sendiri.
function Header() {
const { data } = useQuery({ queryKey: ["user"], queryFn: fetchUser })
return <p>Halo, {data?.name}</p>
}
function Sidebar() {
const { data } = useQuery({ queryKey: ["user"], queryFn: fetchUser })
return <img src={data?.avatar} alt="avatar" />
}Meskipun Header dan Sidebar sama-sama memanggil fetchUser, network request hanya terjadi satu kali. queryKey: ["user"] yang identik di kedua komponen membuat library menggabungkan observer-nya menjadi satu entri cache — inilah deduplication.
Sebuah entri cache lahir saat pertama kali di-request, hidup selama ada observer atau masih dalam gcTime, dan dibuang jika tidak dipakai melebihi gcTime. Saat entri dibuang, referensi queryClient.setQueryData apa pun yang menunjuk ke sana menjadi tidak berguna — data harus di-fetch ulang.
Kadang kalian butuh membaca atau menulis cache tanpa menunggu query:
const cached = queryClient.getQueryData(["todos"])
queryClient.setQueryData(["todos"], (old) => (old ?? []).concat(newTodo))queryClient.getQueryData(["todos"]) membaca data cache tanpa fetch, dan queryClient.setQueryData menulisnya langsung. Kedua method ini akan sangat dipakai untuk optimistic update di episode 11.
Saat cache diperbarui, TanStack Query membandingkan struktur data lama dan baru. Jika ada bagian yang secara struktur identik, bagian itu tetap mereferensi objek yang sama. Ini menjaga React.memo dan useMemo bekerja optimal karena prop yang tidak berubah tidak memicu render ulang.
const oldData = { todos: [{ id: 1 }], meta: { page: 1 } }
const newData = { todos: [{ id: 1 }], meta: { page: 2 } }
oldData.todos === newData.todos // truePerbandingan oldData.todos === newData.todos bernilai true walau objek induknya baru — array todos yang tidak berubah dipertahankan referensinya. structuralSharing diaktifkan secara default dan bisa dimatikan per query jika kalian memang butuh objek baru setiap kali.
Untuk data yang sangat besar, structural sharing melakukan deep-compare yang bisa terasa berat. TanStack Query menyediakan jalur keluar: atur structuralSharing: false pada query tertentu yang datanya selalu berubah, atau berikan function kustom. Untuk kebanyakan kasus, biarkan default karena manfaatnya jauh lebih besar.
Tip
Struktur query key adalah keputusan desain, bukan sekadar kebiasaan. Desain key yang baik (domain → id → sub-resource) membuat invalidasi lebih mudah dan cache lebih mudah dinalar. Luangkan waktu menyusun konvensi key sebelum aplikasi tumbuh besar.
Episode 5 menjelaskan bahwa query key adalah identitas cache, bukan sekadar label. Kalian sekarang memahami array hierarkis dan objek filter, cara deduplication menggabungkan fetch, siklus hidup entri cache, serta structural sharing yang menjaga performa render.
Inti yang harus dibawa pulang:
getQueryData dan setQueryData untuk akses cache langsung.Di episode 6 selanjutnya kita akan membahas mutations — operasi tulis dengan useMutation, status isPending dan isError, serta cara menyinkronkan cache dengan invalidateQueries dan setQueryData setelah mutasi berhasil. Inilah jembatan antara membaca dan menulis data.