Sebuah halaman web tidak hanya mampu untuk menampilkan data yang diambil dari server, tapi juga mampu untuk melakukan penambahan data baru (create), mengubah data (update), dan juga menghapus data yang ada (delete). Contohnya adalah menambahkan produk ke dalam keranjang di website e-commerce.
const ProductDetailPage = () => {
const [isLoading, setIsLoading] = useState(false)
const buy = async (productId) => {
try {
setIsLoading(true)
const response = await axios.post('/orders', {
product_id: productId,
})
alert('Produk berhasil ditambahkan ke dalam keranjang.')
} catch (error) {
alert('Produk gagal ditambahkan ke dalam keranjang.')
} finally {
setIsLoading(false)
}
}
return (
<div>
{/* Kode untuk menampilkan detail produk disini. */}
{!isLoading && (
<button type="button" onClick={() => buy(10)}>
Tambahkan
</button>
)}
{isLoading && (
// Tampilkan loading disini.
)}
</div>
)
}
Bisa dilihat kalo implementasi tersebut memiliki banyak baris kode apalagi jika kita membutuhkan logic yang sama untuk halaman lain dari sebuah website. Belum lagi kalo kita perlu menerapkan sebuah teknik bernama optimistic update. Pasti bakal ribet implementasi nya.
Solusi
Selain untuk proses mengambil data, React Query juga berguna untuk melakukan penambahan data baru (create), mengubah data (update), dan juga menghapus data yang ada (delete). Pertama kita buat sebuah custom hook di sebuah file, misalnya use-buy-product-mutation.js.
const action = async (payload) => {
const response = await axios.post('/orders', {
product_id: payload.productId,
})
return response
}
const useBuyProductMutation = () => useMutation(action)
Kemudian kita gunakan custom hook yang udah kita buat tadi pada sebuah entah itu halaman ataupun komponen manapun yang membutuhkannya.
const ProductDetailPage = () => {
const buyProductMutation = useBuyProductMutation()
return (
<div>
{/* Kode untuk menampilkan detail produk disini. */}
{!buyProductMutation.isLoading && (
<button
type="button"
onClick={() => {
buyProductMutation.mutate(
{
productId: 10
},
{
onSuccess: () => {
alert('Produk berhasil ditambahkan ke dalam keranjang.')
},
onError: () => {
alert('Produk gagal ditambahkan ke dalam keranjang.')
},
}
)
}}>
Tambahkan
</button>
)}
{buyProductMutation.isLoading && (
// Tampilkan loading disini.
)}
</div>
)
}
Kemudian kita bisa juga menerapkan teknik yang disebut dengan optimistic update seperti berikut jika dibutuhkan. Optimistic update tidak hanya untuk penambahan data (create) saja, tapi juga bisa untuk mengubah data (update) ataupun untuk menghapus data yang ada (delete). Implementasi untuk optimistic update memang sedikit ribet, tapi jauh lebih ribet lagi jika tidak menggunakan React Query.
const action = async (payload) => {
const response = await axios.post('/orders', {
product_id: payload.productId,
})
return response
}
const useBuyProductMutation = () => {
const queryClient = useQueryClient()
return useMutation(action, {
onMutate: async (payload) => {
// Batalkan query yang untuk mendapatkan data daftar pesanan.
await queryClient.cancelQueries(['orders'])
// Dapatkan data daftar pesanan yang sudah di-fetch
// dan masuk ke cache sebelumnya.
// Anggap aja kalian udah implementasi query untuk mendapatkan
// daftar pesanan: useOrdersQuery.
const previousOrders = queryClient.getQueryData(['orders'])
// Dapatkan data account diri sendiri yang sedang login
// yang sudah di-fetch dan masuk ke cache sebelumnya.
// Anggap aja kalian udah implementasi query untuk mendapatkan
// account diri sendiri yang login: useMeQuery.
const previousMe = queryClient.getQueryData(["me"])
// Dapatkan value yang di passing sebagai argumen
// ke .mutate() method waktu digunakan
// pada sebuah halaman atau komponen.
const {
productId,
slug,
title,
price,
thumbnail
} = payload
// Perlu menambahkan data palsu (dummy) ke cache milik
// query dengan query key ['orders'] yaitu useOrdersQuery
// selagi proses request ke /orders berjalan.
if (previousOrders && previousMe) {
const newOrder = {
id: Date.now(),
user_id: previousMe.id,
product_id: productId,
title,
price,
thumbnail,
}
const data = [
...previousOrders,
newOrder,
]
queryClient.setQueryData(['orders'], data)
}
return { previousOrders }
},
onError: (error, payload, context?: Context) => {
// Jika terjadi error saat melakukan request ke /orders,
// data daftar pesanan yang ada di cache kembali seperti semula
// untuk menghapus data palsu tadi.
if (context?.previousOrders) {
queryClient.setQueryData(['orders'], context.previousOrders)
}
},
onSettled: () => {
// Setelah berhasil maupun error, perlu melakukan sinkronisasi
// untuk mendapatkan data daftar pesanan terkini.
// Query dengan query key ['orders'] yaitu useOrdersQuery
// akan melakukan fetching data di background.
queryClient.invalidateQueries(['orders'])
},
})
}
Kemudian dapat kita gunakan seperti berikut.
const ProductDetailPage = () => {
const buyProductMutation = useBuyProductMutation();
return (
<div>
{/* Kode untuk menampilkan detail produk disini. */}
<button
type="button"
disabled={buyProductMutation.isLoading}
onClick={() => {
buyProductMutation.mutate(
{
productId: 10,
title: "Product A",
price: 500,
thumbnail: "IMAGE_URL_HERE",
},
{
onSuccess: () => {
alert("Produk berhasil ditambahkan ke dalam keranjang.");
},
onError: () => {
alert("Produk gagal ditambahkan ke dalam keranjang.");
},
}
);
}}
>
Tambahkan
</button>
</div>
);
};
Untuk memahami terkait teknik optimistic update, bisa membaca artikel ini.
Kesimpulan
Disini bisa kita lihat bahwa React Query tidak hanya bermanfaat untuk fetching data saja, melainkan juga bisa membantu kita untuk melakukan penambahan data baru (create), mengubah data (update), dan juga menghapus data yang ada (delete). Diatas hanya memjelaskan sebagian kecil hal yang React Query bisa lakukan, karena fitur yang disuguhkan oleh React Query itu cukup banyak. Sekali lagi saya bilang kalau memang diawal terasa menyulitkan, tapi percayalah, React Query sangat berguna dan layak kalian pertimbangkan untuk dipelajari.