PART 3 — Implementasi State Management Keranjang Belanja Menggunakan Zustand

Pada part sebelumnya, kita sudah belajar dan praktik bagaimana melakukan implementasi API di Frontend Next.js menggunakan arsitektur bertingkat (Services → Features → Components).
Pada pertemuan kali ini, kita akan melanjutkan pembahasan dengan mengimplementasikan state management menggunakan Zustand untuk mengelola keranjang belanja (shopping cart).

Zustand merupakan salah satu state management library yang ringan, sederhana, dan sangat cocok digunakan untuk aplikasi skala kecil-menengah. Sebelum masuk ke implementasi, kamu bisa mempelajari dokumentasinya pada link berikut: https://zustand.docs.pmnd.rs/

Mari kira mulai bagaimana langkah-langkah detail nya.

  • Membuat Struktur Store
Pertama, buat folder dengan nama stores dalam folder src lalu buat file dengan nama cart.store.js
Jika sudah, struktur project akan terlihat seperti Gambar 1 berikut:
Gambar 1

  • Membuat Struktur Dasar Store
Masukkan kode fungsi dasar berikut terlebih dahulu:
import { create } from "zustand";

export const useCartStore = create(
    (set, get) => ({
        // state
    })
);
Kode ini adalah kerangka awal untuk menyimpan state keranjang dan action seperti tambah, update, hingga hapus.

  • Membuat Struktur Dasar Store
Selanjutnya, ubah isi file tersebut menjadi seperti berikut:
import toast from "react-hot-toast";
import { create } from "zustand";

export const useCartStore = create(
    (set, get) => ({
        carts: [],

        addToCart: (product) =>
            set((state) => {
                // jika stok produk kosong
                if (!product.stock) {
                    toast.error("Maaf, produk ini sedang habis");
                    return state;
                }

                // jika produk sudah ada, tambahkan quantity (maksimal stok)
                const existing = state.carts.find((item) => item.id === product.id);

                if (existing) {

                    // jika jumlah produk di keranjang melebihi stok
                    if (existing.quantity + 1 > product.stock) {
                        toast.error("Maaf, jumlah produk di keranjang melebihi stok yang tersedia");
                        return state;
                    }

                    toast.success("Produk berhasil ditambahkan ke keranjang");
                    return {
                        carts: state.carts.map((item) =>
                            item.id === product.id
                                ? {
                                    ...item,
                                    quantity: item.quantity + 1
                                }
                                : item
                        ),
                    };
                }

                // jika produk belum ada di keranjang
                toast.success("Produk berhasil ditambahkan ke keranjang");
                return {
                    carts: [
                        ...state.carts,
                        { ...product, quantity: 1, isSelect: false },
                    ],
                };
            }),

        updateCart: (cart) => {
            set((state) => ({
                carts: state.carts.map((item) =>
                    item.id === cart.id ? { ...item, ...cart } : item
                ),
            }));
        },

        removeFromCart: (id) => {
            toast.success("Produk berhasil dihapus dari keranjang");
            set((state) => ({
                carts: state.carts.filter((item) => item.id !== id),
            }));
        },

        clearCart: () => {
            toast.success("Produk berhasil di bersihkan dari keranjang");
            set({ carts: [] });
        },

        buy: () => {
            toast.success("Produk berhasil dibeli. Terimakasih sudah berbelanja di toko kami.");
            set((state) => ({ carts: state.carts.filter((item) => !item.isSelect) }));
        },
    })
);
Pada kode ini, kita sudah membuat: 
State carts untuk menyimpan data keranjang, Action seperti: addToCart, updateCart, removeFromCart, clearCart, dan buy.

  • Membuat Struktur Dasar Store
carts: [],
Berfungsi menyimpan daftar produk yang ditambahkan user ke keranjang.

addToCart: (product) =>
            set((state) => {
                // jika stok produk kosong
                if (!product.stock) {
                    toast.error("Maaf, produk ini sedang habis");
                    return state;
                }

                // jika produk sudah ada, tambahkan quantity (maksimal stok)
                const existing = state.carts.find((item) => item.id === product.id);

                if (existing) {

                    // jika jumlah produk di keranjang melebihi stok
                    if (existing.quantity + 1 > product.stock) {
                        toast.error("Maaf, jumlah produk di keranjang melebihi stok yang tersedia");
                        return state;
                    }

                    toast.success("Produk berhasil ditambahkan ke keranjang");
                    return {
                        carts: state.carts.map((item) =>
                            item.id === product.id
                                ? {
                                    ...item,
                                    quantity: item.quantity + 1
                                }
                                : item
                        ),
                    };
                }

                // jika produk belum ada di keranjang
                toast.success("Produk berhasil ditambahkan ke keranjang");
                return {
                    carts: [
                        ...state.carts,
                        { ...product, quantity: 1, isSelect: false },
                    ],
                };
            }),
Menambahkan produk baru atau menambah jumlah jika produk sudah ada.

 updateCart: (cart) => {
            set((state) => ({
                carts: state.carts.map((item) =>
                    item.id === cart.id ? { ...item, ...cart } : item
                ),
            }));
        },
Untuk menaikkan/menurunkan jumlah produk dalam keranjang.

removeFromCart: (id) => {
            toast.success("Produk berhasil dihapus dari keranjang");
            set((state) => ({
                carts: state.carts.filter((item) => item.id !== id),
            }));
        },
Menghapus satu item dari keranjang.

clearCart: () => {
            toast.success("Produk berhasil di bersihkan dari keranjang");
            set({ carts: [] });
        },
Membersihkan seluruh isi keranjang.

buy: () => {
            toast.success("Produk berhasil dibeli. Terimakasih sudah berbelanja di toko kami.");
            set((state) => ({ carts: state.carts.filter((item) => !item.isSelect) }));
        },
Simulasi tombol pembelian produk.


  • Menggunakan Store di Komponen
Setelah store selesai dibuat, berikut implementasi di beberapa komponen UI:

A. Navbar – Menampilkan Jumlah Keranjang
Buka file src/components/ui/navbar/FrontpageNavbar.jsx, kemudian ubah code nya menjadi seperti berikut:
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useEffect, useRef, useState } from "react";
import { FaBagShopping } from "react-icons/fa6";
import Button from "../button/Button";
import { useCartStore } from "@/stores/cart.store";
import { useShallow } from "zustand/react/shallow";

export default function FrontpageNavbar() {
  const [isOpenDropdown, setIsOpenDropdown] = useState(false);
  const navbarRef = useRef(null);
  const pathname = usePathname();

  const { carts } = useCartStore(
    useShallow((state) => ({
      carts: state.carts,
    }))
  );

  // click outside
  useEffect(() => {
    const handleClickOutside = (event) => {
      if (navbarRef.current && !navbarRef.current.contains(event.target)) {
        setIsOpenDropdown(false);
      }
    };
    document.addEventListener("mousedown", handleClickOutside);
    return () => {
      document.removeEventListener("mousedown", handleClickOutside);
    };
  }, [navbarRef]);

  return (
    <nav
      ref={navbarRef}
      className="bg-white dark:bg-gray-900 fixed w-full z-20 top-0 start-0 border-b border-gray-200 dark:border-gray-600"
    >
      <div className="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
        <Link
          href="/"
          className="flex items-center space-x-3 rtl:space-x-reverse"
        >
          <img src="/favicon.ico" className="h-8" alt="Flowbite Logo" />
          <span className="self-center text-lg sm:text-2xl font-semibold whitespace-nowrap dark:text-white">
            Simple Ecommerce
          </span>
        </Link>
        <div className="flex md:order-2 space-x-3 md:space-x-0 rtl:space-x-reverse">
          <Link href="/carts">
            <Button className={"text-xs"}>
              <FaBagShopping className="size-4" />
              <span>{carts.length}</span>
            </Button>
          </Link>
          <button
            data-collapse-toggle="navbar-sticky"
            type="button"
            className="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"
            aria-controls="navbar-sticky"
            aria-expanded="false"
            onClick={() => setIsOpenDropdown(!isOpenDropdown)}
          >
            <span className="sr-only">Open main menu</span>
            <svg
              className="w-5 h-5"
              aria-hidden="true"
              xmlns="http://www.w3.org/2000/svg"
              fill="none"
              viewBox="0 0 17 14"
            >
              <path
                stroke="currentColor"
                strokeLinecap="round"
                strokeLinejoin="round"
                strokeWidth="2"
                d="M1 1h15M1 7h15M1 13h15"
              />
            </svg>
          </button>
        </div>
        <div
          className={`items-center justify-between ${
            isOpenDropdown ? "" : "hidden"
          } w-full md:flex md:w-auto md:order-1`}
          id="navbar-sticky"
        >
          <ul className="flex flex-col p-4 md:p-0 mt-4 font-medium border border-gray-100 rounded-lg bg-gray-50 md:space-x-8 rtl:space-x-reverse md:flex-row md:mt-0 md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700">
            <li>
              <Link
                href="/"
                className={`
      block py-2 px-3 rounded-sm 
      ${
        pathname === "/"
          ? "text-white bg-blue-700 md:bg-transparent md:text-blue-700 md:dark:text-blue-500"
          : "text-gray-900 hover:bg-gray-100 md:hover:bg-transparent md:hover:text-blue-700 md:dark:hover:text-blue-500 dark:text-white dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent"
      }
    `}
              >
                Home
              </Link>
            </li>
            <li>
              <Link
                href="/products"
                className={`
      block py-2 px-3 rounded-sm 
      ${
        pathname.startsWith("/products")
          ? "text-white bg-blue-700 md:bg-transparent md:text-blue-700 md:dark:text-blue-500"
          : "text-gray-900 hover:bg-gray-100 md:hover:bg-transparent md:hover:text-blue-700 md:dark:hover:text-blue-500 dark:text-white dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent"
      }
    `}
              >
                Produk
              </Link>
            </li>
          </ul>
        </div>
      </div>
    </nav>
  );
}
Pada kode ini kita menggunakan state carts untuk menampilkan jumlah produk di keranjang
const { carts } = useCartStore(
    useShallow((state) => ({
      carts: state.carts,
    }))
  );
Kemudian menggunakan nya di komponen untuk mengambil jumlah produk yang ada pada keranjang nya
<Link href="/carts">
  <Button className={"text-xs"}>
    <FaBagShopping className="size-4" />
    <span>{carts.length}</span>
  </Button>
</Link>
Penggunaan useShallow disarankan untuk menghindari unnecessary re-render.
Referensi video:
https://youtu.be/8Aj_dX8iPQ0?si=fdOSFJrRKk6ZWQwI&t=639

B. ProductCard – Tombol Add to Cart
Buka file src/components/ui/card/ProductCard.jsx, kemudian ubah kode nya menjadi seperti berikut:
import Link from "next/link";
import React from "react";
import Button from "../button/Button";
import { FaBagShopping } from "react-icons/fa6";
import { useCartStore } from "@/stores/cart.store";
import { useShallow } from "zustand/react/shallow";

export default function ProductCard({ product }) {
  const { addToCart } = useCartStore(
    useShallow((state) => ({
      addToCart: state.addToCart,
    }))
  );

  return (
    <div className="w-full bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700 hover:shadow-xl shadow-gray-300 transition-all duration-300 ">
      <Link href={`/products/${product.id}`}>
        <img
          className="p-4 rounded-t-lg h-72 w-full object-contain"
          src={product.image}
          alt="product image"
        />
      </Link>
      <div className="px-5 pb-5 flex flex-col gap-2">
        <Link href={`/products/${product.id}`}>
          <h5 className="text-xl font-semibold tracking-tight text-gray-900 dark:text-white">
            {product.name}
          </h5>
        </Link>
        <div className="flex justify-between gap-2 items-center">
          <span className="bg-blue-100 text-blue-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded-sm dark:bg-gray-700 dark:text-blue-400 border border-blue-400">
            {product.category.name}
          </span>
          <span className="text-gray-700 text-base">
            {product.stock} Tersedia
          </span>
        </div>
        <div className="flex items-center justify-between">
          <span className="text-xl font-bold text-gray-900 dark:text-white">
            {new Intl.NumberFormat("id-ID", {
              style: "currency",
              currency: "IDR",
              minimumFractionDigits: 0,
            }).format(parseFloat(product.price))}
          </span>
          <Button
            className={`text-xs ${product.stock === 0 && "opacity-50"}`}
            onClick={() => addToCart(product)}
            disabled={product.stock === 0}
          >
            <FaBagShopping className="size-4" />
            Tambah ke Keranjang
          </Button>
        </div>
      </div>
    </div>
  );
}

Di sini kita memanggil addToCart dari store yang sudah dibuat dan menggunakan nya di tombol tambah ke keranjang.

C. ProductCartCard – Update & Hapus Keranjang
Buka file src/components/ui/card/ProductCartCard.jsx, kemudian ubah kode nya menjadi seperti berikut:
import { useCartStore } from "@/stores/cart.store";
import Link from "next/link";
import React from "react";
import { FaTrashAlt } from "react-icons/fa";
import { FaCircleMinus, FaCirclePlus } from "react-icons/fa6";
import { useShallow } from "zustand/react/shallow";

export default function ProductCartCard({ productCart }) {
  const { removeFromCart, updateCart } = useCartStore(
    useShallow((state) => ({
      removeFromCart: state.removeFromCart,
      updateCart: state.updateCart,
    }))
  );

  return (
    <div className="flex gap-4 bg-white px-4 py-6 rounded-md shadow-sm border border-gray-200">
      <div className="flex items-center w-fit">
        <input
          id="checked-checkbox"
          type="checkbox"
          defaultChecked={productCart.isSelect}
          onChange={() =>
            updateCart({ ...productCart, isSelect: !productCart.isSelect })
          }
          className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
        />
      </div>
      <div className="flex gap-6 sm:gap-4 max-sm:flex-col">
        <div className="w-24 h-24 max-sm:w-24 max-sm:h-24 shrink-0">
          <img
            src={productCart.image}
            alt="product image"
            className="w-full h-full object-contain border rounded-sm border-gray-200"
          />
        </div>
        <div className="flex flex-col gap-4">
          <div>
            <Link href={`/products/${productCart.id}`}>
              <h3 className="text-sm sm:text-base font-semibold text-slate-900 hover:underline">
                {productCart.name}
              </h3>
            </Link>
            <span className="bg-blue-100 text-blue-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded-sm dark:bg-gray-700 dark:text-blue-400 border border-blue-400">
              {productCart.category.name}
            </span>
          </div>
          <div className="mt-auto">
            <h3 className="text-sm font-semibold text-slate-900">
              {new Intl.NumberFormat("id-ID", {
                style: "currency",
                currency: "IDR",
                minimumFractionDigits: 0,
              }).format(parseFloat(productCart.price * productCart.quantity))}
            </h3>
          </div>
        </div>
      </div>

      <div className="ml-auto flex flex-col">
        <div className="flex items-start gap-4 justify-end">
          <button
            type="button"
            onClick={() =>
              confirm("Apakah anda yakin menghapus data ini?") &&
              removeFromCart(productCart.id)
            }
          >
            <FaTrashAlt className="text-red-500" />
          </button>
        </div>
        <div className="flex items-center gap-3 mt-auto">
          <button
            type="button"
            onClick={() =>
              updateCart({ ...productCart, quantity: productCart.quantity - 1 })
            }
            disabled={productCart.quantity === 1}
          >
            <FaCircleMinus
              className={`size-4 ${
                productCart.quantity === 1 ? "text-gray-400" : "text-gray-800"
              }`}
            />
          </button>
          <span className="font-semibold text-base leading-[18px]">
            {productCart.quantity}
          </span>
          <button
            type="button"
            onClick={() =>
              updateCart({ ...productCart, quantity: productCart.quantity + 1 })
            }
            disabled={productCart.quantity === productCart.stock}
          >
            <FaCirclePlus
              className={`size-4 ${
                productCart.quantity === productCart.stock
                  ? "text-gray-400"
                  : "text-gray-800"
              }`}
            />
          </button>
        </div>
      </div>
    </div>
  );
}
Pada kode tersebut kita sudah menggunakan removeFromCart untuk hapus produk dari keranjang, dan updateCart untuk update jumlah produk dalam keranjang

D. SummaryOrderCard – Ringkasan Belanja
Buka file src/components/ui/card/SummaryOrderCard.jsx, kemudian ubah kode nya menjadi seperti berikut:
import { useCartStore } from "@/stores/cart.store";
import React from "react";
import { useShallow } from "zustand/react/shallow";

export default function SummaryOrderCard() {
  const { carts, buy } = useCartStore(
    useShallow((state) => ({
      carts: state.carts,
      buy: state.buy,
    }))
  );

  const SHIPMENT_COST = 0;

  const subTotal = carts
    .filter((item) => item.isSelect)
    .reduce((total, item) => total + parseFloat(item.price) * item.quantity, 0);

  const total = subTotal + SHIPMENT_COST;

  const cartsIsSelect = carts.filter((item) => item.isSelect);

  return (
    <div className="bg-white rounded-md px-4 py-6 h-max shadow-sm border border-gray-200">
      <ul className="text-slate-500 font-medium space-y-4">
        <li className="flex flex-wrap gap-4 text-sm">
          Subtotal{" "}
          <span className="ml-auto font-semibold text-slate-900">
            {new Intl.NumberFormat("id-ID", {
              style: "currency",
              currency: "IDR",
              minimumFractionDigits: 0,
            }).format(parseFloat(subTotal))}
          </span>
        </li>
        <li className="flex flex-wrap gap-4 text-sm">
          Pengiriman{" "}
          <span className="ml-auto font-semibold text-slate-900">
            {new Intl.NumberFormat("id-ID", {
              style: "currency",
              currency: "IDR",
              minimumFractionDigits: 0,
            }).format(parseFloat(SHIPMENT_COST))}
          </span>
        </li>
        <hr className="border-slate-300" />
        <li className="flex flex-wrap gap-4 text-sm font-semibold text-slate-900">
          Total{" "}
          <span className="ml-auto">
            {new Intl.NumberFormat("id-ID", {
              style: "currency",
              currency: "IDR",
              minimumFractionDigits: 0,
            }).format(parseFloat(total))}
          </span>
        </li>
      </ul>
      <div className="mt-8 space-y-4">
        <button
          type="button"
          className={`text-sm px-4 py-2.5 w-full font-medium tracking-wide bg-blue-800 hover:bg-blue-900 text-white rounded-md cursor-pointer ${
            !cartsIsSelect.length && "opacity-50 cursor-not-allowed"
          }`}
          onClick={() =>
            confirm("Anda yakin ingin membeli produk ini?") && buy()
          }
          disabled={!cartsIsSelect.length}
        >
          Beli Sekarang
        </button>
      </div>
    </div>
  );
}
Pada kode tersebut kita sudah menggunakan state carts untuk menghitung ringkasan belanja, dan action buy untuk melakukan proses beli produk yang dipilih

E. CartPage – Menampilkan Keranjang
Buka file src/components/pages/cart/CartPage.jsx, kemudian ubah kode nya menjadi seperti berikut:
"use client";
import FrontpageLayout from "@/components/layouts/FrontpageLayout";
import Button from "@/components/ui/button/Button";
import ProductCartCard from "@/components/ui/card/ProductCartCard";
import SummaryOrderCard from "@/components/ui/card/SummaryOrderCard";
import FetchError from "@/components/ui/error/FetchError";
import { useCartStore } from "@/stores/cart.store";
import Link from "next/link";
import { FaArrowRightLong } from "react-icons/fa6";
import { useShallow } from "zustand/react/shallow";

export default function CartPageContent() {
  const { carts, clearCart } = useCartStore(
    useShallow((state) => ({
      carts: state.carts,
      clearCart: state.clearCart,
    }))
  );

  return (
    <FrontpageLayout>
      <h3 className="font-bold text-gray-800 text-3xl text-start px-4 mb-4">
        Keranjang
      </h3>
      {!carts.length ? (
        <div className="flex flex-col gap-4 items-center">
          <FetchError text="Produk tidak ditemukan, silahkan tambahkan produk ke keranjang" />
          <Link href={"/products"}>
            <Button>
              Belanja Sekarang
              <FaArrowRightLong />
            </Button>
          </Link>
        </div>
      ) : (
        <>
          <div
            key={carts.length}
            className="grid lg:grid-cols-3 lg:gap-x-8 gap-x-6 gap-y-8 mt-6 px-4"
          >
            <div className="lg:col-span-2 space-y-6">
              {carts.map((cart, index) => (
                <ProductCartCard key={index} productCart={cart} />
              ))}
            </div>

            <SummaryOrderCard />
          </div>
          <div className="p-4">
            <button
              type="button"
              onClick={() =>
                confirm("Apakah anda yakin ingin bersihkan keranjang?") &&
                clearCart()
              }
              className="focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900"
            >
              Bersihkan Keranjang
            </button>
          </div>
        </>
      )}
    </FrontpageLayout>
  );
}
Pada kode tersebut kita sudah menggunakan state carts untuk menampilkan list produk, dan action clearCart untuk membersihkan produk dari keranjang

  • Membuat Struktur Dasar Store

Sekarang silakan jalankan project dan coba fitur-fitur berikut:

    • Menambahkan produk ke keranjang
    • Melihat isi keranjang
    • Mengubah jumlah produk
    • Menghapus produk
    • Melakukan "pembelian"

Jika berhasil, tampilannya akan seperti pada video demo berikut:


  • Kenapa data Keranjang Hilang Saat Refresh?

Jika anda perhatikan di akhir video, ketika halaman di-refresh, data keranjang hilang.

Ini terjadi karena state Zustand hanya disimpan di memory, sehingga akan hilang saat browser reload.

Untuk mengatasi hal ini, kita harus menyimpan data keranjang di LocalStorage.
Untungnya Zustand menyediakan middleware yang sangat mudah digunakan: https://zustand.docs.pmnd.rs/middlewares/persist

  • Menambahkan Persist ke Store
Buka file src/stores/cart.store.js, kemudian ubah kode nya menjadi seperti berikut:
import toast from "react-hot-toast";
import { create } from "zustand";
import { persist } from "zustand/middleware";

export const useCartStore = create(
    persist(
        (set, get) => ({
            carts: [],

            addToCart: (product) =>
                set((state) => {
                    // jika stok produk kosong
                    if (!product.stock) {
                        toast.error("Maaf, produk ini sedang habis");
                        return state;
                    }

                    // jika produk sudah ada, tambahkan quantity (maksimal stok)
                    const existing = state.carts.find((item) => item.id === product.id);

                    if (existing) {

                        // jika jumlah produk di keranjang melebihi stok
                        if (existing.quantity + 1 > product.stock) {
                            toast.error("Maaf, jumlah produk di keranjang melebihi stok yang tersedia");
                            return state;
                        }

                        toast.success("Produk berhasil ditambahkan ke keranjang");
                        return {
                            carts: state.carts.map((item) =>
                                item.id === product.id
                                    ? {
                                        ...item,
                                        quantity: item.quantity + 1
                                    }
                                    : item
                            ),
                        };
                    }

                    // jika produk belum ada di keranjang
                    toast.success("Produk berhasil ditambahkan ke keranjang");
                    return {
                        carts: [
                            ...state.carts,
                            { ...product, quantity: 1, isSelect: false },
                        ],
                    };
                }),

            updateCart: (cart) => {
                set((state) => ({
                    carts: state.carts.map((item) =>
                        item.id === cart.id ? { ...item, ...cart } : item
                    ),
                }));
            },

            removeFromCart: (id) => {
                toast.success("Produk berhasil dihapus dari keranjang");
                set((state) => ({
                    carts: state.carts.filter((item) => item.id !== id),
                }));
            },

            clearCart: () => {
                toast.success("Produk berhasil di bersihkan dari keranjang");
                set({ carts: [] });
            },

            buy: () => {
                toast.success("Produk berhasil dibeli. Terimakasih sudah berbelanja di toko kami.");
                set((state) => ({ carts: state.carts.filter((item) => !item.isSelect) }));
            },
        }),
        {
            name: "carts-storage", // nama key di localStorage
        }
    )
);

Pada kode ini kita sudah:

    • Menggunakan persist()
    • Menyimpan data di LocalStorage
    • Menggunakan key: "carts-storage"

Sekarang cobalah kembali di browser.
Maka meskipun halaman di-refresh, data keranjang tetap tersimpan.
Hasilnya akan seperti pada video demo berikut:


Selamat! 🎉

Kita sudah berhasil mengimplementasikan:

  • State management menggunakan Zustand
  • Aksi tambah, hapus, update, clear cart, dan beli sekarang
  • Integrasi store ke UI
  • Persist untuk menyimpan data ke LocalStorage

Ini adalah part terakhir dari topik Simple Ecommerce dengan NextJs, Zustand, dan Laravel 

Selanjutnya Anda bisa melakukan pengembangan lanjutan seperti:

  • Menambahkan "Add to Cart" di halaman detail
  • Membuat modal konfirmasi yang lebih bagus
  • Membuat alur transaksi yang lebih realistis
  • Integrasi autentikasi
  • Dan lainnya sesuai dengan keinginan masing-masing

Sekian topik praktisi kita pada pertemuan kali ini. Semoga materi yang telah dipelajari dan dipraktikkan dapat dipahami dengan baik. Mohon maaf apabila terdapat kekurangan dalam penyampaian maupun penulisan artikel ini. Terima kasih dan sampai jumpa pada kesempatan berikutnya.