Halo teman-teman semua, pada artikel sebelumnya, kita telah belajar bagaimana cara melakukan proses insert data menggunakan hook useMutation
dari TanStack Query di React. Nah, pada artikel kali ini, kita akan melanjutkan pembahasan dengan mempelajari cara melakukan proses edit dan update data menggunakan kombinasi useQuery
dan useMutation
.
Skenarionya seperti ini: kita akan mengambil detail data berdasarkan ID menggunakan useQuery
, lalu data tersebut akan kita tampilkan dalam sebuah form. Setelah itu, kita bisa mengubah data sesuai kebutuhan, kemudian menyimpannya kembali ke server menggunakan useMutation
.
Langkah 1 - Edit dan Update Data Dengan useQuery
& useMutation
Silahkan teman-teman buat file baru dengan nama edit.jsx
di dalam folder src/views/products
, kemudian masukkan kode berikut ini di dalamnya.
src/views/products/edit.jsx
// import hook useState, useEffect dari react
import { useState, useEffect } from "react";
// import useNavigate dan useParams dari react-router
import { useNavigate, useParams } from "react-router";
// import useMutation dan useQuery dari react-query
import { useMutation, useQuery } from "@tanstack/react-query";
// import api dari folder api
import Api from "../../api";
const ProductEdit = () => {
// State for image, title, description, price, stock and errors
const [image, setImage] = useState(null);
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [price, setPrice] = useState("");
const [stock, setStock] = useState("");
const [errors, setErrors] = useState({});
// Initialize useNavigate
const navigate = useNavigate();
// Initialize useParams
const { id } = useParams();
// fetch detail product by id
const { data, isLoading, isError } = useQuery({
// Set the query key
queryKey: ['product', id],
// Set the query function
queryFn: async () => {
const res = await Api.get(`/api/products/${id}`);
return res.data.data;
}
});
// Then use useEffect to handle the data when it changes
useEffect(() => {
if (data) {
setTitle(data.title);
setDescription(data.description);
setPrice(data.price);
setStock(data.stock);
}
}, [data]);
//function to handle file change
const handleFileChange = (e) => {
if (e.target.files && e.target.files[0]) {
setImage(e.target.files[0]);
}
};
// Initialize useMutation
const mutationProduct = useMutation({
//mutation function
mutationFn: async (formData) => {
// Call the API to update the product by id
return await Api.post(`/api/products/${id}`, formData);
},
onSuccess: () => {
// Redirect to the product index page
navigate("/products");
},
onError: (error) => {
// set errors if there is an error
setErrors(error.response.data);
},
});
//function to handle form submission
const updateProduct = (e) => {
e.preventDefault();
// Initialize formData
const formData = new FormData();
// Append data to formData
if (image) formData.append("image", image);
formData.append("title", title);
formData.append("description", description);
formData.append("price", price);
formData.append("stock", stock);
formData.append("_method", "PUT");
// Call the mutationProduct function
mutationProduct.mutate(formData);
};
return (
<div className="container mt-5">
<div className="row">
<div className="col-md-12">
<div className="card border-0 rounded-3 shadow">
<div className="card-body">
{/* Loading State */}
{isLoading && (
<div className="alert alert-info text-center">Loading product...</div>
)}
{/* Error State */}
{isError && (
<div className="alert alert-danger text-center">
Error loading product. Please try again.
</div>
)}
<form onSubmit={updateProduct}>
<div className="mb-3">
<label className="form-label fw-bold">Image</label>
<input
type="file"
onChange={handleFileChange}
className="form-control"
/>
{errors.image && (
<div className="alert alert-danger mt-2">
{errors.image[0]}
</div>
)}
</div>
<div className="mb-3">
<label className="form-label fw-bold">Title</label>
<input
type="text"
className="form-control"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Title Product"
/>
{errors.title && (
<div className="alert alert-danger mt-2">
{errors.title[0]}
</div>
)}
</div>
<div className="mb-3">
<label className="form-label fw-bold">Description</label>
<textarea
className="form-control"
value={description}
onChange={(e) => setDescription(e.target.value)}
rows={5}
placeholder="Description Product"
/>
{errors.description && (
<div className="alert alert-danger mt-2">
{errors.description[0]}
</div>
)}
</div>
<div className="row">
<div className="col-md-6">
<div className="mb-3">
<label className="form-label fw-bold">Price</label>
<input
type="number"
className="form-control"
value={price}
onChange={(e) => setPrice(e.target.value)}
placeholder="Price Product"
/>
{errors.price && (
<div className="alert alert-danger mt-2">
{errors.price[0]}
</div>
)}
</div>
</div>
<div className="col-md-6">
<div className="mb-3">
<label className="form-label fw-bold">Stock</label>
<input
type="number"
className="form-control"
value={stock}
onChange={(e) => setStock(e.target.value)}
placeholder="Stock Product"
/>
{errors.stock && (
<div className="alert alert-danger mt-2">
{errors.stock[0]}
</div>
)}
</div>
</div>
</div>
<button
type="submit"
className="btn btn-md btn-primary rounded-5 shadow border-0"
disabled={mutationProduct.isPending}
>
{mutationProduct.isPending ? "Updating..." : "Update"}
</button>
</form>
</div>
</div>
</div>
</div>
</div>
);
};
export default ProductEdit;
Dari penambahan kode di atas, pertama kita import hook useState
dan useEffect
dari React.
// import hook useState, useEffect dari react
import { useState, useEffect } from "react";
Kemudian kita import hook useNavigate
dan useParams
dari React Router.
// import useNavigate dan useParams dari react-router
import { useNavigate, useParams } from "react-router";
Setelah itu, kita import hook useMutation
dan useQuery
dari TanStack Query.
// import useMutation dan useQuery dari react-query
import { useMutation, useQuery } from "@tanstack/react-query";
Terakhir, kita import service API.
// import api dari folder api
import Api from "../../api";
Di dalam function component ProductEdit
, kita membuat beberapa state.
// State for image, title, description, price, stock and errors
const [image, setImage] = useState(null);
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [price, setPrice] = useState("");
const [stock, setStock] = useState("");
const [errors, setErrors] = useState({});
Setelah itu, kita lakukan inisialisasi hook navigate
. Dan destruct id
dari hook useParams
.
// Initialize useNavigate
const navigate = useNavigate();
// Initialize useParams
const { id } = useParams();
Selanjutnya, kita melakukan proses data fetching untuk mendapatkan data product berdasarkan ID menggunakan useQuery
.
// fetch detail product by id
const { data, isLoading, isError } = useQuery({
// Set the query key
queryKey: ['product', id],
// Set the query function
queryFn: async () => {
const res = await Api.get(`/api/products/${id}`);
return res.data.data;
}
});
Di atas, kita destruct fitur-fitur dari useQuery
, yaitu data
, isLoading
dan isError
.
const { data, isLoading, isError } = useQuery({
//...
});
Kemudian di dalamnya kita buat queryKey
dengan nama product
.
// Set the query key
queryKey: ['product', id],
Dan untuk queryFn
, kita lakukan proses fetch data berdasarkan ID ke dalam REST API dengan endpoint /api/products/:id
dan method yang digunakan adalah GET
.
const res = await Api.get(`/api/products/${id}`);
Setelah itu, kita gunakan hook useEffect
untuk memasukkan data
yang didapatkan dari proses fetch menggunakan useQuery
ke dalam state-state yang sudah kita buat sebelumnya.
useEffect(() => {
if (data) {
setTitle(data.title);
setDescription(data.description);
setPrice(data.price);
setStock(data.stock);
}
}, [data]);
Selanjutnya, kita buat function untuk menghandle file change.
<input
type="file"
onChange={handleFileChange}
className="form-control"
/>
//function to handle file change
const handleFileChange = (e) => {
if (e.target.files && e.target.files[0]) {
setImage(e.target.files[0]);
}
};
Selanjutnya, kita melakukan inisialisasi hook useMutation
dengan nama mutationProduct
.
// Initialize useMutation
const mutationProduct = useMutation({
//mutation function
mutationFn: async (formData) => {
// Call the API to update the product by id
return await Api.post(`/api/products/${id}`, formData);
},
onSuccess: () => {
// Redirect to the product index page
navigate("/products");
},
onError: (error) => {
// set errors if there is an error
setErrors(error.response.data);
},
});
Di dalamnya, untuk mutationFn
kita panggil REST API untuk mengupdate data berdasarkan product ID.
//mutation function
mutationFn: async (formData) => {
// Call the API to update the product by id
return await Api.post(`/api/products/${id}`, formData);
},
Jika berhasil, maka onSuccess
akan dieksekusi dan di dalamnya kita lakukan redirect ke halaman products index.
onSuccess: () => {
// Redirect to the product index page
navigate("/products");
},
Jika gagal, maka onError
akan dieksekusi dan di dalamnya kita lakukan set response dari REST API ke state errors
.
onError: (error) => {
// set errors if there is an error
setErrors(error.response.data);
},
Selanjutnya, kita membuat function dengan nama updateProduct
. Fungsi ini akan dijalankan ketika form disubmit.
<form onSubmit={updateProduct}>
//...
</form>
//function to handle form submission
const updateProduct = (e) => {
//...
}
Di dalamnya. kita melakukan inisialisasi FormData
dan melakukan append
data ke dalamnya.
// Initialize formData
const formData = new FormData();
// Append data to formData
if (image) formData.append("image", image);
formData.append("title", title);
formData.append("description", description);
formData.append("price", price);
formData.append("stock", stock);
formData.append("_method", "PUT");
Dan kita panggil mutationProduct.mutate(formData)
dengan mengirimkan formData
.
// Call the mutationProduct function
mutationProduct.mutate(formData);
Langkah 2 - Konfigurasi Route Products Edit
Silahkan teman-teman buka file src/routes/index.jsx
, kemudian ubah kode-nya menjadi seperti berikut ini.
src/routes/index.jsx
// Import React Router
import { Routes, Route } from "react-router";
// Import view HomePage
import Home from "../views/home";
//import view ProductIndex
import ProductIndex from "../views/products/index";
// Import view ProductCreate
import ProductCreate from "../views/products/create";
// Import view ProductEdit
import ProductEdit from "../views/products/edit";
// Definisikan component dengan (Functional Component)
const RoutesIndex = () => {
return (
<Routes>
{/* Route untuk halaman utama */}
<Route path="/" element={<Home />} />
{/* Route untuk halaman produk */}
<Route path="/products" element={<ProductIndex />} />
{/* Route untuk halaman tambah produk */}
<Route path="/products/create" element={<ProductCreate />} />
{/* Route untuk halaman edit produk */}
<Route path="/products/edit/:id" element={<ProductEdit />} />
</Routes>
);
};
export default RoutesIndex;
Dari perubahan kode di atas, kita import view products edit.
// Import view ProductEdit
import ProductEdit from "../views/products/edit";
Kemudian kita buat konfigurasi route-nya dengan path /products/edit/:id
.
{/* Route untuk halaman edit produk */}
<Route path="/products/edit/:id" element={<ProductEdit />} />
Langkah 3 - Uji Coba Edit dan Update Data
Silahkan teman-teman klik button EDIT
yang ada pada data yang dimiliki, jika berhasil maka akan menampilkan halaman form edit data seperti berikut.

Silahkan sesuaikan isinya, kemudian klik button Update
. Jika berhasil maka kita akan diarahkan ke halaman products index dengan data yang telah diperbarui.

Kesimpulan
Pada tutorial kali ini, kita telah belajar bagaimana cara mengedit dan mengupdate data ke API menggunakan kombinasi useQuery
dan useMutation
dari TanStack Query di React.
Pada artikel selanjutnya, kita akan belajar bagaimana cara menghapus data menggunakan useMutation
, serta bagaimana melakukan invalidate query agar data yang ditampilkan tetap up-to-date.
Terima Kasih