Overview

The Tapcart searchClient allows blocks to fetch product, recommendation, and filter data from different integrations. The searchClient follows a strategy pattern with a base class (BaseSearchClient) that provides common functionality and abstract methods that concrete implementations must provide. This architecture allows for extensive customization while maintaining consistency across different search providers. You can use and customize the searchClient at the block level. You can share the searchClient across all blocks on the layout, or create instances of the searchClient for individual blocks.

The following doc outlines:

  • Core Architecture & Types
  • Common methods
  • Access patterns
  • Tapcart Patterns
  • Customizations

Supported Integrations

IntegrationStatusAvailability
AlgoliasupportedAll Pages and Blocks
NostosupportedAll Pages and Blocks
Search SpringsupportedAll Pages and Blocks
Fast SimonsupportedAll Pages and Blocks
Google Searchcoming soonNone
Searchanisecoming soonNone

Core Architecture & Types

The searchClient uses TypeScript interfaces and abstract classes to provide a consistent API across different search providers.

Core Interfaces

interface SearchParams {
  query?: string
  sort?: string
  filters?: Record<string, Set<string>>
  collection?: string
  page?: number
  productsPerPage?: number
}

interface SearchResponse<T = any> {
  products: T[]
  page: number
  totalPages: number
  totalProducts: number
  facets?: Record<string, any>
  nextPage?: number | null
  hasMore: boolean
}

interface QueryParams {
  collectionHandle?: string
  collectionId?: string
  sort_by?: string
  filter?: string[]
  searchQuery?: string
  [key: string]: any
}

interface FormattedFilter {
  title: string
  image: string | null
  tag: string
  amount: number
  isSelected: boolean
  collectionId: string | null
  min: number | null
  max: number | null
}

interface FormattedFacet {
  title: string
  filters: FormattedFilter[]
  field: string | null
  multiSelect: boolean | null
  id: string | null
}

interface SearchConfig {
  "sort-options"?: Array<{
    label: string
    key: string
  }>
  "filter-options"?: Array<{
    label: string
    key: string
  }>
  enabled: boolean
  name: string
  [key: string]: any
}

Base Search Client Structure

// Base search client interface
export abstract class BaseSearchClient<T = any> {
  // Public methods for state management
  // Protected methods for customization
  // Abstract methods that are implemented by the integration classes
}

Data Flow

graph TD
    A[Code.js Block] --> B[useSearchContext/useSearchInstance]
    B --> C[Search Client Instance]
    C --> D[useInfiniteScroll/useRecommendations]
    D --> E[searchClient.fetcher/getRecommendations]
    E --> F[Provider API]
    F --> G[Response Processing]
    G --> H[Product Data]
    H --> I[UI Rendering]

    C --> J[State Management]
    J --> K[Filters/Sort/Collection]
    K --> L[applyBaseParamsToIntegration]
    L --> E

Best Practices

  1. Always bind methods when overriding to preserve this context
  2. Call original methods when extending functionality to maintain base behavior
  3. Handle errors gracefully in custom implementations
  4. Clean up event listeners to prevent memory leaks
  5. Cache expensive operations in custom implementations
  6. Validate input parameters in custom methods