Vue Storefront is now Alokai! Learn More
Add product to Cart

Add Product to Cart

Just getting the data from the API is not enough. Ecommerce websites are feature rich and one of the most important features is the ability to add products to the cart. In this guide, we will learn how to add products to the cart in Alokai Next.js application.

Create Sdk context and hook

So far, we've been using getSdk to access the SDK on the server side. We can also use it on the client side. However, this result in the SDK being reinitialized on every call. For better performance, we'll create a context and hook to hold SDK instance.

First we need to export Sdk type from sdk.ts. Add this line at the end of the file.

+ export type Sdk = ReturnType<typeof getSdk>;

Next, we can define the SDK context and provider by creating apps/storefront/sdk/alokai-context.tsx file with the following content:

"use client";

import { createContext } from "react";
import { Sdk } from "./sdk";

export const SdkContext = createContext<Sdk | null>(null);

export function SdkProvider({
  children,
  sdk,
}: {
  children: React.ReactNode;
  sdk: Sdk;
}) {
  return <SdkContext.Provider value={sdk}>{children}</SdkContext.Provider>;
}

Create an apps/storefront/providers/providers.tsx file that will aggregate all the providers. The file should have the following content:

"use client";

import { ReactNode } from "react";

import { SdkProvider } from "@/sdk/alokai-context";
import { getSdk } from "@/sdk/sdk";
import CartContextProvider from "../providers/CartContextProvider";

export function Providers({ children }: { children: ReactNode }) {
  return (
    <SdkProvider sdk={getSdk()}>
      {children}
    </SdkProvider>
  );
}

Now, add the Providers to the layout.tsx:

+ import { Providers } from "@/providers/providers";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Vue Storefront Next.js Starter",
  description: "Vue Storefront Next.js Starter",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}): JSX.Element {
  return (
    <html lang="en">
      <body className={inter.className}>
+         <Providers>
          {children}
+         </Providers>
      </body>
    </html>
  );
}

Next, create useSdk hook in apps/storefront/hooks/useSdk.ts:

import { SdkContext } from "@/sdk/alokai-context";
import { useContext } from "react";

export const useSdk = () => {
  const contextSdk = useContext(SdkContext);
  if (!contextSdk) {
    throw new Error("useSdk must be used within a SdkProvider");
  }
  return contextSdk;
};

Add to Cart

First, let's understand the process of adding a product to the cart. When a user clicks on the "Add to Cart" button, the product is added to the cart by it's cartId. The cart is a collection of products that the user wants to buy. The cart is stored in the backend and the frontend communicates with the backend to add products to the cart.

In order to achieve this, we will use Alokai SDK's createCartEntry method. This sends a request to Alokai Middleware and the middleware will add the product to the cart.

But that's not all. Since we are building a real-world application, we need to have a cart in a global state. So, let's first do some preparation.

Preparation

We will create a new CartContextProvider component that will provide the cart to the entire application. We will also create a new useCart hook that will be used to access the cart from any component.

First, let's create a new CartContextProvider component. Create a new file inside storefront/providers/CartContextProvider.tsx and add the following code.

"use client";

import { useSdk } from "@/hooks/useSdk";
import { Cart } from "@vsf-enterprise/sap-commerce-webservices-sdk";
import { createContext, useEffect, useState } from "react";

export const CartContext = createContext<{
  cart: Cart;
  updateCart: (cart: Cart) => void;
}>({
  cart: {} as Cart,
  updateCart: () => {},
});

export default function CartContextProvider({
  children,
}: {
  children: React.ReactNode;
}) {
  const [cart, setCart] = useState<Cart>({} as Cart);
  const sdk = useSdk();

  useEffect(() => {
    async function getCart() {
      let cartId = localStorage.getItem("cartId") ?? "";
      let cart: Cart;
      if (!cartId) {
        const { data } = await sdk.sapcc.createCart({});
        cart = data;
      } else {
        try {
          const { data } = await sdk.sapcc.getCart({ cartId: cartId });
          cart = data;
        } catch {
          const { data } = await sdk.sapcc.createCart({});
          cart = data;
        }
      }
      localStorage.setItem("cartId", cart.guid ?? "");

      setCart(cart);
    }

    getCart();
  }, []);

  function updateCart(updatedCart: Cart) {
    setCart(updatedCart);
  }

  return (
    <CartContext.Provider value={{ cart, updateCart }}>
      {children}
    </CartContext.Provider>
  );
}

This Provider has to be a client component, so we can use useState and useEffect hooks. We are using localStorage to store the cartId.

Now, let's create a new useCart hook. Create a new file inside storefront/hooks/useCart.ts and add the following code.

import { Product } from "@vsf-enterprise/sap-commerce-webservices-sdk";
import { useContext } from "react";
import { CartContext } from "../providers/CartContextProvider";
import { useSdk } from "./useSdk";

export default function useCart() {
  const { cart, updateCart } = useContext(CartContext);
  const sdk = useSdk();

  async function addToCart(product: Product, quantity: number = 1) {
    try {
      await sdk.sapcc.createCartEntry({
        cartId: cart.guid as string,
        orderEntry: {
          quantity: quantity,
          product: {
            code: product.code as string,
          },
        },
      });

      const { data } = await sdk.sapcc.getCart({
        cartId: cart.guid as string,
      });

      updateCart(data);
    } catch (error) {
      console.error("Error adding to cart", error);
    }
  }

  return {
    cart,
    addToCart,
  };
}

This hook will be used to access the cart and add products to the cart. We are using the useContext hook to access the cart from the CartContextProvider component and keeping the cart updated in the global state.

Now, let's add our new CartContextProvider to the provider.tsx file to wrap the entire application with the cart context.

+import CartContextProvider from "../providers/CartContextProvider";

export function Providers({ children }: { children: ReactNode }) {
  return (
    <SdkProvider sdk={getSdk()}>
+     <CartContextProvider>
        {children}
+     </CartContextProvider>
    </SdkProvider>
  );
}

We need to keep CartContextProvider inside SdkProvider because we are using the useSdk hook inside the CartContextProvider.

Now, we can use the useCart hook to access the cart and add products to the cart from any component.

Add to Cart Button

Since ProductDetails component already has the product data and SfButton component that is responsible for adding the product to the cart, we can add the useCart hook to the ProductDetails component and use it to add the product to the cart.

// ... rest of the code

export default function ProductDetails({ product }: ProductDetailsProps) {
  const inputId = useId();
  const { addToCart } = useCart(); // Add this line
  const min = 1;
  const max = product.stock?.stockLevel ?? 1;
  const [value, { inc, dec, set }] = useCounter(min);

// ... rest of the code

Now, we can use the addToCart function to add the product to the cart when the user clicks on the "Add to Cart" button.

// ... rest of the code

<SfButton onClick={async () => await addToCart(product, 1)} size="lg" className="w-full xs:ml-4" slotPrefix={<SfIconShoppingCart size="sm" />}>
  Add to cart
</SfButton>

// ... rest of the code

Now, when the user clicks on the "Add to Cart" button, the product will be added to the cart and the cart will be updated in the global state.

Let's test it!

Test it

Let's create a simple NavBar component with a Cart icon that will display the number of products in the cart. Create a new file inside storefront/components/NavBar.tsx and add the following code.

"use client"
import { SfBadge, SfButton, SfIconShoppingCart } from "@storefront-ui/react";
import Link from "next/link";
import useCart from "../hooks/useCart";

export default function NavBar() {
  const { cart } = useCart();

  return (
    <div className="flex items-center justify-between px-8 py-3 bg-primary-700">
      <nav className="flex gap-4 items-center">
        <Link href="/" className="text-white">Home</Link>
        <Link href="/cart" className="text-white">Cart</Link>
      </nav>

      <SfButton className="relative" square variant="tertiary">
        <SfIconShoppingCart className="text-white" />
        <SfBadge content={cart.totalUnitCount} />
      </SfButton>
    </div>
  )
}

Now, let's add the NavBar component to the layout.tsx file.

// ... rest of the code
+ import NavBar from "../components/NavBar";

// ... rest of the code

<Providers>
+ <NavBar />
  {children}
</Providers>

// ... rest of the code

Now, when the user adds a product to the cart, the number of products in the cart will be displayed in the NavBar component. The cart will be updated in the global state and the user will be able to see the number of products in the cart from any page.

Here's how it looks like:

Add to Cart

That's it! We have successfully added the product to the cart and displayed the number of products in the cart in the NavBar component.

Congratulations! You have successfully added products to the cart in Alokai Next.js application. 🎉

You can find a complete code for this guide in the add-to-cart branch of the Alokai Next.js Starter.

Summary

In this guide, we learned how to add products to the cart in Alokai Next.js application. We created a new CartContextProvider component that provides the cart to the entire application and a new useCart hook that is used to access the cart from any component. We also added the NavBar component that displays the number of products in the cart.

If you followed along with this guide, you might have noticed that we added Cart route to the NavBar component. You already know how to create a new page and display the products in the cart. If you want to challenge yourself, try to create a new page that displays the products in the cart and allows the user to remove products from the cart. I recommend using Product Card Horizontal Storefront UI component to display the products in the cart.

We are done with the basics of Alokai Next.js application.

You can now move on to the next guide to learn more advanced concepts and features of Alokai or start building your own Alokai Next.js application and come back later to learn more advanced concepts.

Next: Unified Data Layer

Learn how to install, configure and use Unified Data Layer