Building the Product Tile/Card

Overview

The product tile/card is the most important component of the storefront. It is the component that allows the user to browse through the products that are available for purchase. It is also the component that allows the user to view product details and add products to their cart.

In the previous section, we learned how to create a Product Listing Page. We demonstrated how to use the alternativeSearch query to retrieve a list of products from the Merchstack API. If you have not read the previous section, we recommend that you do so before continuing.

In this section, we will learn how to use the alternativeSearch query to retrieve a list of products from the Merchstack API and display them in a product tile/card. We will also learn how to use the alternativeSearch query to retrieve a list of products from the Merchstack API and display them in a product tile/card with variants.

We will try to build the product tile/card in the most generic way possible but if we do deviate from the generic approach, we will provide an explanation as to what we are doing and why we are doing it.

Step by Step guide to create a product tile/card

In this section, we will walk through the process of creating a product tile/card with and without variants. We will start by only displaying the product name and price. We will then add the product image and description. Finally, we will add the product variants. We will use the alternativeSearch query to retrieve the product data from the Merchstack API. We will be using Tailwind CSS to style the product tile/card. Tailwind CSS is a utility-first CSS framework. If you are not familiar with Tailwind CSS, we recommend that you read the Tailwind CSS documentation although it is not required. The class names that we will be using are self-explanatory but if it is not clear you can refer to the Tailwind CSS documentation for more information or simply Google the class name.

So, let us get started. We will start by creating a new file called ProductTile.jsx assuming that you are using React. If not, create a file of your choice based on the framework that you are using. Please note that we will be using JSX syntax in this section which is very similar to HTML. You should be able to follow along even if you are not using React and convert the JSX syntax to the syntax of the framework that you are using.

We will start by importing the alternativeSearch query from the Merchstack API. We will also import the useQuery hook from the Apollo Client which we will use to execute the alternativeSearch query.

import React from 'react'
import { useQuery, gql } from '@apollo/client'

const GET_PRODUCTS = gql`
  query AlternativeSearch($input: AlternativeSearchInput!) {
    alternativeSearch(input: $input) {
        items {
          name
          slug
          brand
          variants {
            name
            sku
            stockLevel
            currencyCode
            asset {
              preview
            }
            lineage
            id
          }
          price {
            ...on PriceRange {
              min
              max
            }
          }
          compareAtPrice {
            ...on PriceRange {
              min
              max
            }
          }
          id
          lineage
          code
          stockLevel
          score
          asset {
            id
            preview
          }
          slug
          categoryIds
        }
        totalItems
      }
  }
`

function ProductListing() {
  const { loading, error, data } = useQuery(GET_PRODUCTS, {
    variables: {
      input: {
        inStock: true,
        take: 10,
        skip: 0,
      },
    },
  })
  
  if (loading) return <p>Loading...</p>
  if (error) return <p>Error :(</p>

  return data.alternativeSearch.items.map((product) => (
    /* Product Tile/Card */
  ))
}

export default ProductListing

In the above code example, we have requested the products that are in stock and have limited the number of products to 10. We have also requested the product name, slug, brand, variants, price, compareAtPrice, id, lineage, code, stockLevel, score, asset, slug, and categoryIds. We will use this data to build the product tile/card. At present, if you observe the code example, we are not doing anything with the data. We are simply returning a loading message if the data is loading and an error message if there is an error. If the data is available, we are returning an empty array. We will now start building the product tile/card and replace the comment /* Product Tile/Card */ with the code for the product tile/card.

Let's start by displaying the product name and price. Below is the code for a basic product tile/card.

export const ProductTile = ({ product }) => {
  return (
    <div className="border border-gray-200 rounded-md shadow-md">
      <div className="p-4">
        <h3 className="text-lg font-medium text-gray-900">{product.name}</h3>

        <div className="mt-2">
          <div className="flex items-center">
            <div className="flex items-center">
              <div className="text-gray-900">
                {product.currencyCode} {product.price.min / 100}
              </div>

              {product.compareAtPrice && (
                <div className="ml-2 text-sm line-through text-gray-500">
                  {product.currencyCode} {product.compareAtPrice.min / 100}
                </div>
              )}
            </div>
          </div>
        </div>
      </div>
    </div>
  )
}

In the above code example, we are displaying the product name and price. We are also displaying the compareAtPrice if it is present. We are using the product prop to access the product assuming that the product data is passed to the ProductTile component as a prop. We are also using the currencyCode to display the currency code. We are dividing the price by 100 to convert the price from cents to dollars. This is a very basic product tile/card. We will now add the product image and description and style the product tile/card appropriately.

export const ProductTile = ({ product }) => {
  return (
    <div className="border border-gray-200 rounded-md shadow-md">
      <div className="p-4">
        <div className="flex justify-center">
          <img
            className="object-contain w-64 h-64"
            src={product.asset.preview}
            alt={product.name}
          />
        </div>

        <h3 className="mt-4 text-lg font-medium text-gray-900">
          {product.name}
        </h3>

        <div className="mt-2">
          <div className="flex items-center">
            <div className="flex items-center">
              <div className="text-gray-900">
                {product.currencyCode} {product.price.min / 100}
              </div>

              {product.compareAtPrice && (
                <div className="ml-2 text-sm line-through text-gray-500">
                  {product.currencyCode} {product.compareAtPrice.min / 100}
                </div>
              )}
            </div>
          </div>
        </div>
      </div>
    </div>
  )
}

Users usually want to see the variants, if available, of a product before adding the product to their cart. Let's go with the option to show color variants for now. We will show a list color avatar for each variant under variant image. This would be a clickable avatar which would update the card information with the selected variant. Below is the code for the product tile/card with variants.

export const ProductTile = ({ product }) => {
  const [selectedVariant, setSelectedVariant] = useState(product.variants[0])

  return (
    <div className="border border-gray-200 rounded-md shadow-md">
      <div className="p-4">
        <div className="flex justify-center">
          <img
            className="object-contain w-64 h-64"
            src={selectedVariant.asset.preview}
            alt={selectedVariant.name}
          />
        </div>

        <ul className="mt-4 flex justify-center">
          {product.variants.map((variant) => (
            <li key={variant.id} className="mr-2">
              <button
                className="w-8 h-8 rounded-full border border-gray-200"
                onClick={() => setSelectedVariant(variant)}
              >
                <img
                  className="object-contain w-full h-full"
                  src={variant.asset.preview}
                  alt={variant.name}
                />
              </button>
            </li>
          ))}
        </ul>

        <h3 className="mt-4 text-lg font-medium text-gray-900">
          {product.name}
        </h3>

        <div className="mt-2">
          <div className="flex items-center">
            <div className="flex items-center">
              <div className="text-gray-900">
                {product.currencyCode} {selectedVariant.price / 100}
              </div>

              {product.compareAtPrice && (
                <div className="ml-2 text-sm line-through text-gray-500">
                  {product.currencyCode} {selectedVariant.compareAtPrice.min / 100}
                </div>
              )}
            </div>
          </div>
        </div>
      </div>
    </div>
  )
}

More Information on Alternate Search

The alternativeSearch query is a powerful query that allows you to search for products based on a variety of criteria. For more information on the alternativeSearch query or any other queries, please refer to the GraphQL Playground -> Docs -> alternativeSearch

We will keep referring to the alternativeSearch query throughout the storefront integration documentation. It is important to understand how to use this query. We recommend that you spend some time familiarizing yourself with the query.

Resources

Concepts

Learn about the concepts in the Merchstack platform.

Integrations

Learn about the ins and outs of building a custom integration.

Guides

Follow our useful guides to get up and running quickly.

Getting Help

Learn who to reach out to if you get stuck and need more assistance.