Developer Blog

Adding items to cart

The first step of building a shopping cart is allowing our customers to add items to their cart. In this blog series, we are diving into the ins and outs of building a multi-vendor shopping cart with single-vendor checkout using the Sharetribe Developer Platform.

Mar 4, 2024

A child adding grocery items to a small yellow shopping cart.

The first step of building a shopping cart is allowing our customers to add items to their cart. Since only signed-in customers can start transactions on a Sharetribe marketplace, we can add the same limitation for the cart feature.

When a user is signed in to a marketplace, the application state contains a currentUser object representing the signed-in user. We will save the cart in the currentUser’s private data. We need to make the following changes:

  • Add an “Add to cart button” to OrderPanel
  • Pass the necessary props to OrderPanel from the listing page components (ListingPageCarousel and ListingPageCoverPhoto), or only one of them, depending on your layout
  • Add a toggle cart function to ListingPage.duck.js and map it to props on your listing page component
  • Add the necessary functions on your listing page component as well as ListingPage.shared.js

You can find the code snippets used in this guide in this Gist.

The previous article introduced the high-level view of building a shopping cart with Sharetribe Developer Platform – if you didn't already, read it first!

Include an Add to Cart button in the project


The simplest iteration of an add to cart button would indeed be a <Button/> component. However, let’s include some more functionality within the component, so we can use it in multiple ways in multiple places down the road.

This file contains an example of an AddToCartButton component. There are a few things to note. 

Most importantly, we only want to show the button for products, not bookings. In addition, we only want to show it when the rest of the product form is also shown.

 const AddToCartButton = props => {
  const {
    listing,
    count,
    incrementCart,
    isListingPage,
    buttonLabel,
    cartLabel,
    isBooking = false,
    showProductOrderForm = true,
    isOwnListing = false,
  } = props;

  if (isBooking || !showProductOrderForm) {
    return null;
  }

For products, we then want to show the button differently depending on whether it is already in the user’s cart, and whether the user can keep adding this item to their cart.

  const increaseCount = () => incrementCart(1);
  const decreaseCount = () => incrementCart(-1);
  
  const currentStock = listing?.currentStock?.attributes.quantity;
  const isMaxItems = currentStock <= count;

  if (!count || (count === 0 && currentStock > 0)) {
    return (
      <Button
        onClick={() => {
          if (!isOwnListing) increaseCount();
        }}
      >
        {buttonLabel}
      </Button>
    );
  } else {
    return (
      <div>
        {isListingPage && (cartLabel)}
        <div className={css.buttonContainer}>
          <Button className={css.smallButton} onClick={decreaseCount}>
            -
          </Button>
          <span className={css.countContainer}>{count}</span>
          <Button 
            className={css.smallButton}
            disabled={isMaxItems}
            onClick={increaseCount}
          >
            +
          </Button>
        </div>
      </div>
    );
  }
};

To add the new AddToCartButton component to your Sharetribe Web Template, you will need to do the following:

  • Create a new folder AddToCartButton in src/components
  • Create AddToCartButton.js and AddToCartButton.module.css files in the folder
  • Add an export for the file in src/components/index.js – this way, you can import the button similarly to other components:
export { default as AddToCartButton } from './AddToCartButton/AddToCartButton'

Add the new button to OrderPanel


Once you have made this new component available in your codebase, let’s add it to OrderPanel.js. Add AddToCartButton to the list of imports from ../../components, and include it in the return statement to replace ProductOrderForm:

...
  ) : showProductOrderForm ? (
    <AddToCartButton
      showProductOrderForm={showProductOrderForm}
      isBooking={isBooking}
      buttonLabel={<FormattedMessage id="AddToCartButton.label" />}
      cartLabel={<FormattedMessage id="AddToCartButton.cartLabel" />}
      {...cartProps}
    />
  ) : showInquiryForm ? (
...

Then, add a new prop, cartProps, to the destructuring statement at the start of the component:

const OrderPanel = props => {
  const {
    ...
    cartProps,
  } = props;

Finally, add the necessary marketplace text keys either to your template’s en.json file or to the Marketplace texts editor in Console.

Now, if you visit the listing page of a product listing, you can see the basic version of the component:

Add to cart button in OrderPanel

However, if you try to click the button, it will not do anything yet. Let’s add some functionality next.

Add cart toggling functionality to ListingPage.duck.js


When a customer clicks “Add to cart”, we want to add the listing’s id in the customer’s private data. Since we are building a single vendor checkout logic, we also want to save the provider’s id, so that we know which listings were authored by which provider.

With these requirements, the cart information in the user’s private data could look like this:

cart: {
  // First author ID
  "6451caa1-00b2-41f4-b7aa-ef437f3b088b": {
    // First listing id
    "85168ae9-3560-485d-b618-3d446544035e": {
      "count": 1
    },
    // Second listing id
    "68b59b81-d6ee-4a83-8d43-442f220a7498": {
      "count": 2
    },
  },
  // Second author id
  "d473d01b-2ad8-4cb1-9f05-f2dca397f2e5": {
    // First listing id
    "234f3325-a5e3-4a5e-8574-033c9e6b74a8": {
      "count": 2
    },
  },
}

We could store this information in the application’s state, but it’s better to save it in the customer’s private data with an API call to persist the information properly. To do that, we need to create a thunk for calling the Sharetribe API.

At this point, we have two options:

  • we can create the thunk in the ListingPage.duck.js file
  • or we can introduce a new .duck.js file called CartPage.duck.js

For now, let’s add a simple version of the thunk in ListingPage.duck.js for the sake of focusing on this feature. Later in this series, as we introduce CartPage, we can then move the thunk to the CartPage.duck.js file and expand its functionality.

You can add these imports and functions to src/containers/ListingPage/ListingPage.duck.js.

The thunk does the following things:

  • gets currentUser from state, and gets the current cart from the user’s private data
  • Updates the cart on the correct author, listing, and amount, adding listings and authors where necessary
  • Calls the Sharetribe SDK currentUser.updateProfile endpoint to set the updated cart to the user’s private data
  • With the response from SDK, updates the currentUser in state

Next, we need to connect the button to this thunk. We will make those changes on the listing page.

Update ListingPage.shared.js and your listing page component


The Sharetribe Web Template has two different listing page components, corresponding to two different Console configurations:

  • “Image carousel with thumbnails” layout option uses ListingPageCarousel.js
  • “Screen-wide cover photo” layout option uses ListingPageCoverPhoto.js

In this guide, we will make the changes to ListingPageCarousel.js, but the same changes can be made to ListingPageCoverPhoto.js. In addition, we will make some changes to ListingPage.shared.js, which exports some common functionalities that are used on both listing page components.

Add cart toggling function to listing page props

Let’s start by importing and using the toggleCart function we created in ListingPage.duck.js to ListingPageCarousel.js. First, add it to the import statement from ListingPage.duck.

import {
  ...,
  toggleCart,
} from './ListingPage.duck';

Then, towards the very end of the file, we will need to add it to our existing mapDispatchToProps definition. This allows us to define a prop for our listing page component that essentially dispatches the action we have defined for it.

const mapDispatchToProps = dispatch => ({
  ...,
  onToggleCart: (listingId, authorId, increment) =>
    dispatch(toggleCart(listingId, authorId, increment)),
});

Now that we have an onToggleCart prop, we can add it to the destructuring statement at the start of the component:

export const ListingPageComponent = props => {
...
  const {
    ...,
    onToggleCart,
  } = props;

This allows us to use onToggleCart in the next step.

Add a cart handling function to ListingPage.shared.js

On ListingPage.shared.js, we will add a handleAddToCart function. It basically does two things, depending on whether the user is signed in or not:

  • If the user is not signed in, it redirects them to the login page, and then returns them back to the same listing page
  • If the user is signed in, it calls onToggleCart that we pass to it from ListingPageCarousel.js.

The structure of this function reflects the structure of a lot of the other functions in ListingPage.shared.js – it is a curried function. In other words, it takes its parameters one at a time instead of all at once. 

export const handleAddToCart = parameters => value => {
 ...

This means that when we use the function on the listing component page, we can partially apply this handleAddToCart function by only passing parameters, and that way construct another function that we then pass on as a parameter to AddToCartButton. Let’s see how that works in practice.

Back on ListingPageCarousel.js, add handleAddToCart to the import from ListingPage.shared.js, and then add these functions to the ListingPageCarousel.js component.

We call handleAddToCart with only one set of parameters, and the result is a function we will call onAddToCart. To create onAddToCart, we will pass all the parameters we already know we need, including

  • the listing and author information
  • the onToggleCart function we connected to props earlier
  • and some common parameters that are used in all the ListingPage.shared.js functions.

We can then use the onAddToCart function directly as the incrementCart parameter that we pass to AddToCartButton

  const onAddToCart = handleAddToCart({
      ...commonParams,
      location,
      currentUser,
      onToggleCart,
      listingId: listingId.uuid,
      authorId: currentAuthor.id.uuid,
    });
...

  const cartProps = {
    listing: currentListing,
    count: listingCartCount,
    incrementCart: onAddToCart,
    isListingPage: true,
    isOwnListing,
  };

As a final step, add a new prop cartProps to OrderPanel on the listing component page. Now,  you should be able to add the listing to your user’s shopping cart!

Cart button in OrderPanel with one item selected to cart

Summary


In this guide, we did the following things to add AddToCartButton to a listing page:

  • Add an “Add to cart button” to OrderPanel
  • Pass the necessary props to OrderPanel from the listing page components (ListingPageCarousel and ListingPageCoverPhoto), or only one of them depending on your layout
  • Add a toggle cart function to ListingPage.duck.js and map it to props on your listing page component
  • Add the necessary functions on your listing page component as well as ListingPage.shared.js

In the next instalment of this blog series, we will take a look at creating a cart page for viewing your shopping cart selections!