Tutorial React TanStack Query #7: Edit dan Update Data ke API (useQuery & useMutation)


👍 1 ❤️ 0 💡 0 🔥 0 🙌 0 🥳 0
Tutorial React TanStack Query #7: Edit dan Update Data ke API (useQuery & useMutation)

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


Fika Ridaul Maulayya
Full-Stack Developer, Content Creator and CO-Founder SantriKoding.com

Suka dengan tulisan di SantriKoding? Kamu bisa memberikan dukungan dengan berdonasi atau bagikan konten ini di sosial media. Terima kasih atas dukungan Anda!

KEBIJAKAN KOMENTAR

Saat memberikan komenatar silahkan memberikan informasi lengkap tentang error, seperti: screenshot, link kode, dll. Baca aturan komentar kami