Displaying products (Shopify Hydrogen)

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 as either a part of a product list 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 storefront, 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 users interact with a product.

Define target controller hooks

Define and use controller hooks specific to the product discovery solution you’re building.

Controller hooks let you interact with products and display them in your storefront.

Context Function to use to define the controller hook

Search page

defineProductList

Listing page

defineProductList

Recommendation interface

defineRecommendations

Commerce engine configuration and definition example:

import { defineCommerceEngine } from '@coveo/headless-react/ssr-commerce';

export const engineDefinition = defineCommerceEngine({
  // ...
  controllers: {
    productList: defineProductList(),
    homepageRecommendations: defineRecommendations({
      options: {slotId: '9a75d3ba-c053-40bf-b881-6d2d3f8472db'},
    }),
    cartRecommendations: defineRecommendations({
      options: {slotId: '5a93e231-3b58-4dd2-a00b-667e4fd62c55'},
    }),
    pdpRecommendationsLowerCarousel: defineRecommendations({
      options: {slotId: 'a24b0e9c-a8d2-4d4f-be76-5962160504e2'},
    }),
    pdpRecommendationsUpperCarousel: defineRecommendations({
      options: {slotId: '05848244-5c01-4846-b280-ff63f5530733'},
    }),
    // ...
  }
  // ...
});

// ...

export const {
  // ...
  useProductList,
  useHomepageRecommendations,
  useCartRecommendations,
  usePdpRecommendationsLowerCarousel,
  usePdpRecommendationsUpperCarousel,
  // ...
} = engineDefinition.controllers;

For the full example, see coveo-engine.ts

Access products with target hooks

With the controller hook for your product discovery solution you can display products by accessing the state state.products object on it.

This object contains a list of products (Product[]) that you can render to the user.

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

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

import {engineDefinition} from '~/lib/coveo.engine';
import {Link} from '@remix-run/react';
import type {Product} from '@coveo/headless/ssr-commerce';

export function ProductList() {
  const productList = engineDefinition.controllers.useProductList(); 1

  const logClickEvent = (product: Product) => {
    productList.methods?.interactiveProduct({options: {product}}).select();
  };

  return (
    <ul>
      {productList.state.products.map((product) => ( 2
        <li key={product.permanentid}>
          <Link
            to={`/products/${product.permanentid}`} 3
            onClick={() => logClickEvent(product)} 4
          >
            {product.ec_name}
          </Link>
        </li>
      ))}
    </ul>
  );
}
1 Retrieve the useProductList controller hook.
2 Map over the list of products and render them.
3 When the user selects a product, navigate to the product detail page (PDP).
4 Log a click event when the user interacts with a product via the InteractiveProduct sub-controller.

For a complete example of a product list component, see ProductList.tsx.

To display recommendations, you would use the useHomepageRecommendations, useCartRecommendations, usePdpRecommendationsLowerCarousel, or usePdpRecommendationsUpperCarousel controller hooks instead of useProductList.

import {useHomepageRecommendations} from '@/lib/coveo.engine';
import {ProductCard} from '../Products/ProductCard';

export function Recommendations() {
  const homepageRecommendations = useHomepageRecommendations();

  return (
    <div>
      {homepageRecommendations.state.products.map((recommendation) => ( 1
        <div key={recommendation.permanentid}>
          <ProductCard
            product={recommendation}
            onSelect={
              homepageRecommendations.methods?.interactiveProduct({ 2
                options: {product: recommendation},
              }).select
            }
          />
        </div>
      ))}
    </div>
  );
}
1 Display each of the recommended products.
2 Use a product card component that calls the interactiveProduct function to log click events when the user selects the product and to handle navigation.

For a complete example of home page recommendations, see Recommendations.tsx.

Note

Also, to display recommendations, make sure to enable the target recommendation hook when fetching the static state. See Displaying recommendations with your recommendation provider.

Displaying a product on a product detail page

When displaying a product on product detail page (PDP), define and use a hook on the ProductView controller to ensure that the correct product view events are logged when a user views a product.

Note

The following section focuses on Coveo Headless-specific details of a PDP, particularly logging product view events.

A real Hydrogen project would include additional logic, such as querying Shopify’s Storefront API to retrieve product data, particularly fetching product details and selected variants.

For a complete example of a PDP component, see the Barca Sports Hydrogen project repository.

Create a product view logger component to include in your product detail pages:

import {useEffect, useCallback} from 'react';
import {useProductView} from '~/lib/coveo.engine';

interface ProductViewLoggerProps {
  productName: string;
  productId: string;
  price: number;
}

export function ProductViewLogger({
  productName,
  productId,
  price,
}: ProductViewLoggerProps) {
  const productView = useProductView(); 1

  const logProductView = useCallback(() => { 2
    if (!productView.methods) return;
    productView.methods.view({
      name: productName,
      productId,
      price,
    });
  }, [productView.methods, productName, productId, price]);

  useEffect(() => { 3
    logProductView();
  }, [logProductView]);

  return null;
}
1 Retrieve the useProductView controller hook, which gives you access to the ProductView controller.
2 Define a function that logs a product view event using the view method. Wrap the function in a useCallback hook to prevent unnecessary re-creation on re-renders, improving performance.
3 Use the useEffect hook to call the logProductView function when the component mounts.

Once you’ve created the ProductViewLogger component, include it in your product detail page component:

// app/routes/products.$productId.tsx

import {ProductViewLogger} from '~/components/ProductViewLogger';

// ...

export default function ProductDetailPage() {
  return (
    <div>
      <ProductViewLogger 1
        productName={productName}
        productId={productId}
        price={price}
      />
      {/* Render the rest of the product detail page */}
    </div>
  );
}
1 Include the ProductViewLogger component in your PDP, passing the product’s name, ID, and price.