Belajar TanStack Query - Mutations
Episode 6 of 23

Belajar TanStack Query - Mutations

Episode ini mengajarkan operasi tulis dengan useMutation: status isPending dan isError, callback onSuccess dan onError, serta dua pola sinkronisasi cache yaitu invalidateQueries dan setQueryData setelah mutasi berhasil.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

Membaca data baru separuh perjalanan. Separuh lainnya adalah menulis data — menambah todo, mengubah profil, menghapus item. Di pola manual, operasi tulis juga berantakan: status loading manual, error yang mudah terlewat, dan cache yang tidak pernah diperbarui sehingga halaman menampilkan data basi.

Episode 6 membahas useMutation untuk operasi tulis, lalu dua pola sinkronisasi cache yang membuat UI selalu konsisten dengan server: invalidateQueries dan setQueryData.

useMutation Dasar

Struktur dan Status

useMutation hampir sama dengan useQuery, tapi tidak berjalan otomatis saat mount — dia menunggu dipanggil lewat method mutate atau mutateAsync:

JSMutation untuk menambah todo
import { useMutation, useQueryClient } from "@tanstack/react-query"
 
async function createTodo(title) {
  const res = await fetch("https://jsonplaceholder.typicode.com/todos", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ title, completed: false }),
  })
  if (!res.ok) throw new Error("Gagal membuat todo")
  return res.json()
}
 
function TodoForm() {
  const mutation = useMutation({ mutationFn: createTodo })
 
  return (
    <form
      onSubmit={(event) => {
        event.preventDefault()
        const title = event.target.title.value
        mutation.mutate(title)
      }}
    >
      <input name="title" />
      <button type="submit" disabled={mutation.isPending}>
        {mutation.isPending ? "Menyimpan..." : "Simpan"}
      </button>
      {mutation.isError && <p>Gagal: {mutation.error.message}</p>}
    </form>
  )
}

mutation.mutate(title) memicu createTodo dengan argumen title. Status isPending menandakan operasi sedang berjalan dan cocok untuk mematikan tombol; isError dan error menampilkan kegagalan. useMutation tidak otomatis menulis ke cache — itu tugas kalian berikutnya.

Callback onSuccess dan onError

Kalian bisa bereaksi terhadap keberhasilan atau kegagalan lewat callback yang didefinisikan saat mendeklarasikan mutation:

JSCallback mutation
const mutation = useMutation({
  mutationFn: createTodo,
  onSuccess: (data) => {
    console.log("Todo baru:", data)
  },
  onError: (error) => {
    console.error("Mutasi gagal:", error)
  },
})

onSuccess menerima hasil mutationFn sebagai argumen pertama. Di v5, callback ini hanya tersedia di useMutation, bukan di useQuery — perubahan penting yang harus diingat kalau kalian migrasi dari v4.

Menyinkronkan Cache: invalidateQueries

Menandai Data Basi

Setelah mutasi berhasil, data di cache mungkin sudah tidak mencerminkan server. Cara paling umum untuk memperbarui adalah invalidasi: tandai query tertentu sebagai basi sehingga dia di-refetch otomatis pada kesempatan berikutnya.

JSInvalidasi setelah mutasi
import { useMutation, useQueryClient } from "@tanstack/react-query"
 
function TodoForm() {
  const queryClient = useQueryClient()
  const mutation = useMutation({
    mutationFn: createTodo,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["todos"] })
    },
  })
  return <Form /> // markup di episode sebelumnya
}

queryClient.invalidateQueries({ queryKey: ["todos"] }) menandai semua key berprefix ["todos"] sebagai basi. Karena key hierarkis, query ["todos", 5] dan ["todos", "detail", 5] ikut di-refetch jika sedang aktif. invalidateQueries adalah pola default yang direkomendasikan karena paling sederhana dan sulit salah.

Invalidasi dengan Kecerdasan

Jika mutasi hanya menyentuh satu item, validasi cakupannya agar tidak menembak terlalu banyak query:

JSInvalidasi yang lebih sempit
onSuccess: () => {
  queryClient.invalidateQueries({ queryKey: ["todos", id] })
}

queryKey: ["todos", id] membidik item spesifik, bukan seluruh daftar. invalidateQueries menerima ekspresi key parsial — pemilihan cakupan ini bagian dari seni mengelola performa yang akan kita dalami di episode 13.

Menyinkronkan Cache: setQueryData

Update Lokal Langsung

Untuk UI yang lebih responsif, kalian bisa menulis langsung ke cache dengan setQueryData tanpa menunggu refetch. Ini pola untuk data yang sudah diketahui bentuknya:

JSUpdate cache secara langsung
onSuccess: (newTodo) => {
  queryClient.setQueryData(["todos"], (old) => [...(old ?? []), newTodo])
}

queryClient.setQueryData(["todos"], ...) menggabungkan todo baru ke array yang ada. setQueryData menerima updater function seperti useState — argumen old adalah data cache saat ini, dan nilai kembaliannya menjadi data baru.

Kapan Memilih Antara Keduanya

  • Gunakan invalidateQueries ketika data hasil mutasi tidak diketahui persisnya — misalnya server menghitung ulang agregat atau field.
  • Gunakan setQueryData ketika data hasil mutasi sudah pasti — seperti form yang dikirim dan respons yang kita simpan.
  • Kombinasi keduanya juga umum: setQueryData untuk kecepatan, lalu invalidateQueries untuk memastikan konsistensi final.

Warning

setQueryData menulis cache tanpa memvalidasi bentuk data. Pastikan bentuk data yang ditulis sama dengan yang diharapkan komponen lain, kalau tidak render akan membuang error aneh yang sulit ditelusuri.

Penutup

Episode 6 melengkapi siklus baca-tulis: membaca dengan useQuery, menulis dengan useMutation, lalu menyinkronkan cache dengan invalidateQueries untuk refetch otomatis atau setQueryData untuk update langsung. Kalian sekarang bisa membangun aplikasi yang datanya selalu konsisten.

Inti yang harus dibawa pulang:

  • useMutation dipicu lewat mutate atau mutateAsync, bukan otomatis.
  • isPending mengindikasikan operasi berjalan; isError untuk kegagalan.
  • onSuccess dan onError tersedia di useMutation, bukan useQuery.
  • invalidateQueries menandai query basi agar di-refetch otomatis.
  • setQueryData menulis langsung ke cache untuk update instan.
  • Pilih cakupan invalidasi yang tepat agar tidak memicu fetch berlebih.

Di episode 7 selanjutnya kita akan membahas staleness dan refetching — perbedaan staleTime dan gcTime, serta opsi refetchOnWindowFocus, refetchOnReconnect, refetchInterval, dan refetch manual. Kalian akan paham kapan data di-refetch dan bagaimana mengendalikannya.

Belajar TanStack Query - Mutations | Belajar TanStack Query