JSPM

  • Created
  • Published
  • Downloads 16
  • Score
    100M100P100Q64552F
  • License MIT

React hooks library for Maker Connected Store Product API

Package Exports

  • @makerinc/react-sdk
  • @makerinc/react-sdk/docs

Readme

@makerinc/react-sdk

React hooks library for the Maker Connected Store Product API.

Maker connects to multiple ecommerce platforms (Shopify, FeedSync, and more) through a unified API. This library provides a simple, type-safe way to fetch and manage product data from any connected store using your store_id.

Installation

npm install @makerinc/react-sdk
# or
yarn add @makerinc/react-sdk

Quick Start

1. Wrap your app with StoreProvider

import { StoreProvider } from '@makerinc/react-sdk';

function App() {
  return (
    <StoreProvider
      storeId="my-store" // Your Maker store_id
      
      country="US"
      language="en"
    >
      <YourApp />
    </StoreProvider>
  );
}

2. Use hooks in your components

import { useProduct } from '@makerinc/react-sdk';

function ProductDetail({ productId }) {
  const { data, isLoading, error } = useProduct(productId);

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  const product = data?.products[0];
  return (
    <div>
      <h1>{product.title}</h1>
      <p>{product.description}</p>
      <p>${product.minPrice}</p>
    </div>
  );
}

API Reference

<StoreProvider>

Configuration provider that supplies store settings to all hooks.

Props:

  • storeId (string, required) - Your Maker store identifier (connects to your Shopify, FeedSync, or other platform)
  • baseUrl (string, optional) - Maker API base URL (default: https://api.maker.co)
  • country (string, optional) - Default country code for contextual pricing (e.g., "US")
  • language (string, optional) - Default language code (e.g., "en")
  • currency (string, optional) - Default currency code (e.g., "USD")
  • getHeaders (function, optional) - Function returning custom headers for requests
<StoreProvider
  storeId="my-store"
  baseUrl="https://api.yourdomain.com"
  country="US"
  language="en"
  currency="USD"
  getHeaders={() => ({ 'x-custom-header': 'value' })}
>
  {children}
</StoreProvider>

useProduct(productId, options?)

Fetch a single product by ID.

Parameters:

  • productId (string) - Product ID to fetch
  • options (object, optional):
    • inStock (boolean) - Only return if in stock
    • fmt ('google') - Return Google Product Feed format
    • currency (string) - Currency for prices
    • country (string) - Country for contextual pricing
    • language (string) - Language for localization
    • enabled (boolean) - Enable/disable query execution

Returns: UseQueryResult<GetProductsResponse>

function ProductPage({ id }) {
  const { data, isLoading, error, refetch } = useProduct(id, {
    inStock: true,
    country: 'US',
  });

  return <div>...</div>;
}

useProducts(options)

Fetch multiple products by IDs (max 100).

Options:

  • productIds (string | string[], required) - Product IDs to fetch
  • inStock (boolean)
  • fmt ('google')
  • currency (string)
  • country (string)
  • language (string)
  • enabled (boolean)
function ProductList() {
  const { data, isLoading } = useProducts({
    productIds: ['prod_123', 'prod_456', 'prod_789'],
    inStock: true,
  });

  return (
    <div>
      {data?.products.map((product) => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

useProductsByCategory(options)

Fetch products from a category with filtering, sorting, and pagination. This hook can be used to build both full Product Listing Pages (PLPs) and individual category sliders/carousels.

Options:

  • categoryId (string, required) - Category identifier
  • limit (number) - Results per page (default: 10)
  • page (string) - Pagination cursor
  • sort (SortOrder) - Sort order: 'default', 'price_high', 'price_low', 'newest', 'best_sellers', 'featured', 'discount_high'
  • inStock (boolean) - Filter to in-stock only
  • facets (string | string[]) - Facet filter indexes
  • createdWithin (string) - Date range (e.g., "7d", "2w", "3m")
  • minPrice (number) - Minimum price filter
  • maxPrice (number) - Maximum price filter
  • focalProductId (string) - Center pagination around product
  • currency, country, language (string)
  • enabled (boolean)

Use Case 1: Full Product Listing Page (PLP)

Build a complete category page with filtering, sorting, and pagination:

function CategoryPage({ categoryId }) {
  const [cursor, setCursor] = useState<string | undefined>();
  const [selectedFacets, setSelectedFacets] = useState<string[]>([]);
  const [sortOrder, setSortOrder] = useState<SortOrder>('default');

  const { data, isLoading } = useProductsByCategory({
    categoryId: categoryId,
    sort: sortOrder,
    inStock: true,
    limit: 24,
    page: cursor,
    facets: selectedFacets,
    minPrice: 0,
    maxPrice: 500,
  });

  return (
    <div>
      <h1>Category Products</h1>

      {/* Sort Controls */}
      <select value={sortOrder} onChange={(e) => setSortOrder(e.target.value as SortOrder)}>
        <option value="default">Default</option>
        <option value="price_low">Price: Low to High</option>
        <option value="price_high">Price: High to Low</option>
        <option value="newest">Newest</option>
        <option value="best_sellers">Best Sellers</option>
      </select>

      {/* Facet Filters */}
      {data?.filters?.facets.map(([facet, count]) => (
        <label key={facet}>
          <input
            type="checkbox"
            checked={selectedFacets.includes(facet)}
            onChange={(e) => {
              if (e.target.checked) {
                setSelectedFacets([...selectedFacets, facet]);
              } else {
                setSelectedFacets(selectedFacets.filter((f) => f !== facet));
              }
            }}
          />
          {facet} ({count})
        </label>
      ))}

      {/* Product Grid */}
      <div className="product-grid">
        {data?.products.map((product) => (
          <ProductCard key={product.id} product={product} />
        ))}
      </div>

      {/* Pagination */}
      <div>
        {data?.page.previous && (
          <button onClick={() => setCursor(data.page.previous!)}>Previous</button>
        )}
        {data?.page.next && (
          <button onClick={() => setCursor(data.page.next!)}>Next</button>
        )}
      </div>
    </div>
  );
}

Use Case 2: Category Slider/Carousel

Display a limited set of products in a slider component:

function CategorySlider({ categoryId, title }: { categoryId: string; title: string }) {
  const { data, isLoading } = useProductsByCategory({
    categoryId: categoryId,
    limit: 6, // Only fetch 6 products for slider
    sort: 'best_sellers',
  });

  if (isLoading) return <div>Loading...</div>;
  if (!data?.products.length) return null;

  return (
    <section>
      <h2>{title}</h2>
      <div className="slider">
        {data.products.map((product) => (
          <div key={product.id} className="slide">
            <img src={product.images[0]?.url} alt={product.name || ''} />
            <h3>{product.name}</h3>
            <p>${product.variants[0]?.price || 'N/A'}</p>
          </div>
        ))}
      </div>
    </section>
  );
}

// Usage: Multiple sliders on homepage
function Homepage() {
  return (
    <>
      <CategorySlider categoryId="new-arrivals" title="New Arrivals" />
      <CategorySlider categoryId="best-sellers" title="Best Sellers" />
      <CategorySlider categoryId="on-sale" title="On Sale" />
    </>
  );
}

useSearchProducts(options?)

Full-text product search with pagination.

Options:

  • query (string) - Search query
  • limit (number) - Results per page (default: 20)
  • page (string) - Pagination cursor
  • fmt ('google')
  • country (string)
  • currency (string)
  • enabled (boolean)
function SearchPage() {
  const [query, setQuery] = useState('');
  const { data, isLoading } = useSearchProducts({
    query,
    limit: 20,
  });

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="Search products..."
      />
      {isLoading && <div>Searching...</div>}
      {data?.products.map((product) => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}

Advanced Usage

Custom Headers

Pass custom headers (e.g., for contextual pricing with IP):

<StoreProvider
  storeId="my-store"
  baseUrl="https://api.yourdomain.com"
  getHeaders={() => ({
    'x-forwarded-for': getUserIP(),
  })}
>
  <App />
</StoreProvider>

Pagination

All list endpoints support cursor-based pagination:

function CategoryWithPagination({ categoryId }) {
  const [cursor, setCursor] = useState<string | undefined>();

  const { data } = useProductsByCategory({
    categoryId: categoryId,
    page: cursor,
    limit: 20,
  });

  return (
    <div>
      {data?.products.map((p) => (
        <Product key={p.id} {...p} />
      ))}

      <button
        disabled={!data?.page.previous}
        onClick={() => setCursor(data?.page.previous!)}
      >
        Previous
      </button>

      <button
        disabled={!data?.page.next}
        onClick={() => setCursor(data?.page.next!)}
      >
        Next
      </button>
    </div>
  );
}

Faceted Filtering

Filter products by facets (variant options, tags, metadata):

function FilteredCategory({ categoryId }) {
  const [selectedFacets, setSelectedFacets] = useState<string[]>([]);

  const { data } = useProductsByCategory({
    categoryId: categoryId,
    facets: selectedFacets,
  });

  return (
    <div>
      {/* Show available facets */}
      {data?.filters?.facets.map(([facet, count]) => (
        <label key={facet}>
          <input
            type="checkbox"
            checked={selectedFacets.includes(facet)}
            onChange={(e) => {
              if (e.target.checked) {
                setSelectedFacets([...selectedFacets, facet]);
              } else {
                setSelectedFacets(selectedFacets.filter((f) => f !== facet));
              }
            }}
          />
          {facet} ({count})
        </label>
      ))}

      {/* Show filtered products */}
      {data?.products.map((p) => (
        <Product key={p.id} {...p} />
      ))}
    </div>
  );
}

Contextual Pricing

Enable market-specific pricing (works with Shopify stores via Maker):

<StoreProvider
  storeId="my-store" // Your Maker store_id
  country="CA"
  language="en"
  getHeaders={() => ({
    'x-forwarded-for': getUserIP(), // Required for contextual pricing
  })}
>
  <App />
</StoreProvider>

Conditional Fetching

Disable queries until ready:

function ConditionalProduct({ productId }) {
  const [shouldFetch, setShouldFetch] = useState(false);

  const { data, isLoading } = useProduct(productId, {
    enabled: shouldFetch,
  });

  return (
    <div>
      <button onClick={() => setShouldFetch(true)}>Load Product</button>
      {isLoading && <div>Loading...</div>}
      {data && <ProductDisplay product={data.products[0]} />}
    </div>
  );
}

Type Definitions

The library is fully typed with TypeScript. Import types as needed:

import type {
  Product,
  Variant,
  Category,
  SortOrder,
  UseQueryResult,
} from '@makerinc/react-sdk';

function MyComponent() {
  const { data }: UseQueryResult<GetProductsResponse> = useProducts({
    productIds: ['123'],
  });

  const product: Product | undefined = data?.products[0];
}

Architecture

This is the official React SDK for the Maker Connected Store API.

What is Maker? Maker provides a unified API to access product data from multiple ecommerce platforms including Shopify, FeedSync, and more. Connect any store through a single store_id and access consistent product data regardless of the underlying platform.

The library follows patterns inspired by:

  • Commerce.js - Context-based configuration with granular hooks
  • Shopify Hydrogen - Type-safe, framework-agnostic design
  • TanStack Query - Query state management patterns

Design Principles

  1. Context-based Configuration - Global store settings via StoreProvider
  2. Granular Hooks - One hook per API endpoint for maximum flexibility
  3. Type Safety - Full TypeScript support with types from Maker API
  4. Simple State Management - Built-in loading/error states, no external dependencies
  5. Caching Respect - Works with Maker API's aggressive caching strategy (60s max-age, 3600s stale-while-revalidate)

Contributing

See CONTRIBUTING.md for development guidelines.

License

MIT