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-sdkQuick 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 fetchoptions(object, optional):inStock(boolean) - Only return if in stockfmt('google') - Return Google Product Feed formatcurrency(string) - Currency for pricescountry(string) - Country for contextual pricinglanguage(string) - Language for localizationenabled(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 fetchinStock(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 identifierlimit(number) - Results per page (default: 10)page(string) - Pagination cursorsort(SortOrder) - Sort order:'default','price_high','price_low','newest','best_sellers','featured','discount_high'inStock(boolean) - Filter to in-stock onlyfacets(string | string[]) - Facet filter indexescreatedWithin(string) - Date range (e.g., "7d", "2w", "3m")minPrice(number) - Minimum price filtermaxPrice(number) - Maximum price filterfocalProductId(string) - Center pagination around productcurrency,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 querylimit(number) - Results per page (default: 20)page(string) - Pagination cursorfmt('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
- Context-based Configuration - Global store settings via
StoreProvider - Granular Hooks - One hook per API endpoint for maximum flexibility
- Type Safety - Full TypeScript support with types from Maker API
- Simple State Management - Built-in loading/error states, no external dependencies
- 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