Displaying products

This is for:

Developer

Regardless of the product discovery solution you’re building (search, listings, or recommendations), displaying products to the user is an essential part of the commerce experience.

Products can be displayed either as part of a list of products or as a single product on a product detail page (PDP).

This article explains how to display products and ensure that the correct events, such as clicks and product views, are logged when a user interacts with a product.

Displaying lists of products

In your commerce application, you’ll often need to display a list of products, for instance when showing search results, product recommendations, or product listing pages. It’s important to log click events when a user interacts with a product in these lists.

Accessing products in the controller

To interact with products and display them, use the controller specific to the product discovery solution you’re implementing.

Regardless of the controller you use, you can display products by accessing the state.products object on the controller. This object contains a list of products (Product[]) that you can render to the user.

Once you’ve built one of the controllers listed above and have access to state.products, you can pass the products to a component that will render them.

Note

For an example of how to initialize the Search controller and access products to pass to the ProductList component in the following code snippet, see Displaying the search page with the Search controller.

The following example ProductList component receives a list of products and renders them:

import {
  InteractiveProduct as HeadlessInteractiveProduct,
  InteractiveProductProps,
  Product as HeadlessProduct,
  Cart,
} from '@coveo/headless/commerce';
import InteractiveProduct from '../interactive-product/interactive-product';

interface IProductListProps {
  products: HeadlessProduct[]; 1
  controllerBuilder: ( 2
    props: InteractiveProductProps
  ) => HeadlessInteractiveProduct;
  navigate: (pathName: string) => void; 3
}

export default function ProductList(props: IProductListProps) {
  const {products, controllerBuilder, navigate} = props;

  if (products.length === 0) {
    return null;
  }

  return (
    <ul>
      {products.map((product, index) => ( 4
        <li key={index}>
          <InteractiveProduct 5
            product={product}
            controller={controllerBuilder({options: {product}})}
            navigate={navigate}
          ></InteractiveProduct>
        </li>
      ))}
    </ul>
  );
}
1 ProductList takes a list of products as input, accessed from the product discovery solution controller. This component is agnostic to the specific controller used.
2 A function that builds the controller for the InteractiveProduct component. This function returns an instance of HeadlessInteractiveProduct, which logs the correct analytics event when a product is clicked. It can be accessed through the controller.interactiveProduct method on the product discovery solution controller.
3 Function to navigate to a specific path in the application. This function will be used to navigate to the PDP when a product is clicked.

The implementation details of this function are specific to the application and aren’t covered in this example.

4 Loops through the list of products and renders a component for each product.
5 InteractiveProduct is a custom component that renders a product and logs the correct analytics event when the product is clicked.

For more details on how to implement the InteractiveProduct component, see the next section.

Using the InteractiveProduct Sub-Controller

Every product displayed in a product list—whether on a search page, a product listing page, a recommendations carousel, or an instant products displayed below the search box—must use the InteractiveProduct sub-controller to ensure that the correct analytics event is logged when the product is clicked.

The following code snippet demonstrates how to create an InteractiveProduct component that displays a product and uses the InteractiveProduct sub-controller to log the click event when a product is clicked:

import {
  InteractiveProduct as HeadlessInteractiveProduct,
  Product,
} from '@coveo/headless/commerce';

interface IInteractiveProductProps {
  product: Product;
  controller: HeadlessInteractiveProduct;
  navigate: (pathName: string) => void;
}

export default function InteractiveProduct(props: IInteractiveProductProps) {
  const {product, controller, navigate} = props;

  const clickProduct = () => {
    controller.select(); 1
    const productId = product.ec_product_id ?? product.permanentid;
    navigate(`/product/${productId}`); 2
  };

  return ( 3
    <div>
      <button onClick={clickProduct}>
        {product.ec_name}
      </button>
      <div>
        <img
          src={product.ec_images[0]}
          alt={product.permanentid}
          height={100}
        ></img>
      </div>
       {product.ec_promo_price}
      <div>
        <p>{product.ec_description}</p>
      </div>
    </div>
  );
}
1 Logs the click event using the InteractiveProduct controller when the product is clicked. This controller is passed to the component from the ProductList component.
2 Navigates to the product detail page when the product is clicked. This function is passed to the component from the ProductList component, and the implementation details for this function vary depending on the application.
3 Renders the product information and a button that, when clicked, logs the click event and navigates to the product detail page.

In the above code sample, the logic to interact with the cart and log events based on user interaction has been omitted to keep the code simple.

For more details on implementing this functionality with Headless, see Managing the cart and the sample application in the Headless repository.

Displaying a product on a product detail page

When displaying a product on a product detail page (PDP), use the ProductView controller to ensure that the correct product view event is logged when a user views a product.

When the user navigates to the PDP, product details should be displayed to the user and the ProductView controller should be used to log the product view event.

import {
  CommerceEngine,
  Context,
  buildProductView,
} from '@coveo/headless/commerce';
import {useEffect, useRef} from 'react';
import {loadProduct} from '../utils/pdp-utils';

interface IProductDescriptionPageProps {
  engine: CommerceEngine;
  contextController: Context;
  url: string;
  navigate: (pathName: string) => void;
}

export default function ProductDescriptionPage(props: IProductDescriptionPageProps) {
  const {engine, contextController, url, navigate} = props;

  const productViewEventEmitted = useRef(false); 1
  const product = loadProduct(); 2

  useEffect(() => { 3
    contextController.setView({url});
  }, [contextController, url]);

  useEffect(() => { 4
    if (productViewEventEmitted.current || !product) {
      return;
    }

    buildProductView(engine).view(product);
    productViewEventEmitted.current = true;
  }, [engine, product]);

  if (!product) {
    return null;
  }

  return ( 5
    <div >
      <h2>{product.name}</h2>
      <p>Price: {product.price}</p>
    </div>
  );
}
1 Utilize the useRef hook to track whether the product view event has been logged for the product displayed on the PDP. This hook ensures that the product view event is only logged once per product per page load.
2 The loadProduct function is a placeholder for logic that fetches the product data from a backend service. Implementation details will vary based on your application.
3 Sets the context to keep the state of the URL in sync with Headless.

For more information on why this is important and how to do it, see Navigating between pages.

4 Logs the product view event when the component mounts, if the event hasn’t already been logged and if the product data is available.

Use the buildProductView function to create an instance of the ProductView controller and call the view method by passing in the product data to log the product view event.

5 Renders the product information on the PDP.

PDP pages typically also include functionality that allows products to be added to the cart. This functionality has been omitted to keep the code simple.

For more details on implementing this functionality with Headless, see Managing the cart and the sample application in the Headless repository.