Product listing and search

Compose listing, filtering, connected Search UI, and provider-backed Search runtime while keeping Search GraphQL schema truth in generated sources.

Use this guide when building a PLP, collection grid, search-results page, autocomplete entry point, or connected Search product surface with React SDK components.

This guide covers shopper-facing UI composition and React runtime boundaries. Search GraphQL query shape, fields, filters, facets, generated documents, and response types belong to the Search schema and package reference. Link to Search GraphQL latest when exact query truth matters.

Split the problem

A listing page usually has two independent responsibilities:

  1. Query/data responsibility - route state, search text, filters, sorting, market/store-group/locale context, network request, stale-result handling.
  2. UI composition responsibility - search input, filters, toolbar, grid/list layout, empty state, pagination or infinite loading, product-display cards.

Keep these separate. The SDK component docs help with composition; they are not handwritten schema reference.

Choose the search entry surface

  • Use SearchBar for submit-driven search. It can hand a query to route or page state without requiring live suggestions.
  • Use SearchAutocomplete when the input needs suggestions or previews. Static or app-provided suggestions can be passed directly; live SDK-backed suggestions require root EnadProvider plus the app's client and search configuration.
  • Use SearchPanel when the page needs a composed search control area.
  • Use SearchProductCard, SearchProductRecommendations, or SearchProductVariantSelector only when the app intentionally wants a connected Search-backed product wrapper.
  • Confirm exact exports, props, and hook names in package reference before implementing connected Search behavior.

Provider and live data boundary

A plain visual listing can render inside the normal React SDK shell:

import { EnadProvider } from "@enadhq/enad-react-sdk";

export function ListingShell({ children }: { children: React.ReactNode }) {
  return <EnadProvider>{children}</EnadProvider>;
}

When the listing resolves live Search GraphQL data, add the required shopper-facing client/search configuration at the app runtime boundary. Do not hide that requirement inside SearchBar, SearchAutocomplete, or ProductCard.

The provider-backed Search runtime keeps market, store group, and locale together. Use the same values your Search GraphQL query uses so prices, facets, translations, and product availability stay coherent.

graphql.storeGroupSlug can override groupId for Search context. Use that override when the Search store group should differ from the broader provider group value.

Product mapping and prices

mapSearchProductToProductCard() accepts storeGroupSlug so mapping can prefer the intended store-group price before falling back to another price.

When rendering Search results into product cards, preserve the Search runtime storeGroupSlug and pass it through mapping code when the package helper supports it.

Treat missing matching prices as a data or recovery case. Do not guess another market or store group, and keep cart or checkout decisions outside presentational cards.

Listing composition checklist

  • Route state owns the current query, filters, sort, page, and locale/market/store-group context.
  • Data code owns request cancellation, empty states, errors, stale results, and retry behavior.
  • ListingToolbar, FilterGroup, FilterPanel, and SortSelect organize controls; they do not own Search schema truth.
  • ProductCard displays normalized product data; it does not resolve the product model or perform checkout side effects.
  • InfiniteScroll and pagination controls should reflect app-owned loading state.
  • Product, variant, price, brand, series, tag, attribute, video, file, and collection fields should come from generated Search fragments when available.

Empty and recovery states

A useful PLP handles empty and recovery states explicitly.

  • The user has not entered a query yet.
  • The current query returns zero results.
  • Active filters exclude all products.
  • The search request failed.
  • Stale results are still visible while a new query loads.
  • Product data is missing required display fields.
  • A product has prices, but none match the active storeGroupSlug.
  • Live Search context does not match the route's market, store group, or locale.

Use EmptyState or app-specific copy for recovery. Link users back to clear filters, browse categories, or retry depending on the failure.

Product listing and search

# Product listing and search Use this guide when building a PLP, collection grid, search-results page, autocomplete entry point, or connected Search product surface with React SDK components. > This guide covers shopper-facing UI composition and React runtime boundaries. Search GraphQL query shape, fields, filters, facets, generated documents, and response types belong to the Search schema and package reference. Link to [Search GraphQL latest](/search/latest) when exact query truth matters. ## Split the problem A listing page usually has two independent responsibilities: 1. **Query/data responsibility** - route state, search text, filters, sorting, market/store-group/locale context, network request, stale-result handling. 2. **UI composition responsibility** - search input, filters, toolbar, grid/list layout, empty state, pagination or infinite loading, product-display cards. Keep these separate. The SDK component docs help with composition; they are not handwritten schema reference. ## Choose the search entry surface - Use [SearchBar](/react-sdk/latest/components/commerce/search-bar) for submit-driven search. It can hand a query to route or page state without requiring live suggestions. - Use [SearchAutocomplete](/react-sdk/latest/components/commerce/search-autocomplete) when the input needs suggestions or previews. Static or app-provided suggestions can be passed directly; live SDK-backed suggestions require root `EnadProvider` plus the app's client and search configuration. - Use [SearchPanel](/react-sdk/latest/components/commerce/search-panel) when the page needs a composed search control area. - Use [SearchProductCard](/react-sdk/latest/components/commerce/search-product-card), [SearchProductRecommendations](/react-sdk/latest/components/commerce/search-product-recommendations), or [SearchProductVariantSelector](/react-sdk/latest/components/commerce/search-product-variant-selector) only when the app intentionally wants a connected Search-backed product wrapper. - Confirm exact exports, props, and hook names in package reference before implementing connected Search behavior. ## Provider and live data boundary A plain visual listing can render inside the normal React SDK shell: ```tsx import { EnadProvider } from "@enadhq/enad-react-sdk"; export function ListingShell({ children }: { children: React.ReactNode }) { return {children}; } ``` When the listing resolves live Search GraphQL data, add the required shopper-facing client/search configuration at the app runtime boundary. Do not hide that requirement inside `SearchBar`, `SearchAutocomplete`, or `ProductCard`. The provider-backed Search runtime keeps market, store group, and locale together. Use the same values your Search GraphQL query uses so prices, facets, translations, and product availability stay coherent. `graphql.storeGroupSlug` can override `groupId` for Search context. Use that override when the Search store group should differ from the broader provider group value. ## Product mapping and prices `mapSearchProductToProductCard()` accepts `storeGroupSlug` so mapping can prefer the intended store-group price before falling back to another price. When rendering Search results into product cards, preserve the Search runtime `storeGroupSlug` and pass it through mapping code when the package helper supports it. Treat missing matching prices as a data or recovery case. Do not guess another market or store group, and keep cart or checkout decisions outside presentational cards. ## Listing composition checklist - Route state owns the current query, filters, sort, page, and locale/market/store-group context. - Data code owns request cancellation, empty states, errors, stale results, and retry behavior. - [ListingToolbar](/react-sdk/latest/components/commerce/listing-toolbar), [FilterGroup](/react-sdk/latest/components/commerce/filter-group), [FilterPanel](/react-sdk/latest/components/commerce/filter-panel), and [SortSelect](/react-sdk/latest/components/commerce/sort-select) organize controls; they do not own Search schema truth. - [ProductCard](/react-sdk/latest/components/storefront/product-card) displays normalized product data; it does not resolve the product model or perform checkout side effects. - [InfiniteScroll](/react-sdk/latest/components/commerce/infinite-scroll) and pagination controls should reflect app-owned loading state. - Product, variant, price, brand, series, tag, attribute, video, file, and collection fields should come from generated Search fragments when available. ## Empty and recovery states A useful PLP handles empty and recovery states explicitly. - The user has not entered a query yet. - The current query returns zero results. - Active filters exclude all products. - The search request failed. - Stale results are still visible while a new query loads. - Product data is missing required display fields. - A product has prices, but none match the active `storeGroupSlug`. - Live Search context does not match the route's market, store group, or locale. Use [EmptyState](/react-sdk/latest/components/commerce/empty-state) or app-specific copy for recovery. Link users back to clear filters, browse categories, or retry depending on the failure. ## Related docs - [Search GraphQL latest](/search/latest) - [React SDK setup](/react-sdk/latest/setup) - [Commerce components](/react-sdk/latest/components/commerce) - [ProductCard](/react-sdk/latest/components/storefront/product-card) - [Troubleshooting](/start/troubleshooting)