Every public React component in @numueg/theme-sdk.Provider + boundaries#
<NuMuProvider>#
Root provider. Wraps your theme's component tree. Must be the outermost wrapper inside mount().import { NuMuProvider } from "@numueg/theme-sdk";
export function mount(ctx) {
root.render(
<NuMuProvider {...ctx}>
<App />
</NuMuProvider>
);
}
Sets up: ShopContext, CustomerContext, ThemeSettingsContext, LocalizationContext, PageContext, CartContext. Reads the customer_access_token cookie on mount to hydrate the customer.Props (all from MountContext): store, themeSettings, page, locale, direction, currency.<ProductProvider> / <CollectionProvider>#
Overrides for nested trees. Useful when you render multiple products on one page (collection grid, recommendations slider):<ProductProvider product={p}>
<ProductCard />
</ProductProvider>
Inside the provider, useProduct() returns p.<Section> / <Block>#
Section + block boundaries with built-in <ErrorBoundary>. Resolve the current section/block from useSection().<Section>
{(section) => (
<div className="hero">
<h1>{section.settings.headline}</h1>
{section.block_order?.map(id => <Block key={id} blockId={id} />)}
</div>
)}
</Section>
A section's render function receives the resolved instance; <Block blockId> looks up the block by id in the section's blocks map and renders the registered block component.If a block component errors, the boundary catches it and renders a fallback. This is critical β a broken block must not take down the whole section.UI primitives#
<Image>#
<Image
src="/uploads/abc.jpg"
alt="Product"
width={600}
height={600}
sizes="(min-width: 768px) 50vw, 100vw"
loading="lazy"
/>
Generates srcset via the platform's image-transform service (Phase 4.2). Falls back to the raw URL when the service is unavailable.<Money>#
<Money cents={12999} /> // β "129.99 EGP" (RTL-aware in Arabic locale)
<Money cents={12999} currency="USD" />
Reads locale + direction from context. Respects useCurrency() for multi-currency presentment.<Link>#
<Link to="/products/abc">View product</Link>
<Link to="/cart" external={false}>Cart</Link>
Internal-first. Resolves the URL via useShop().formatUrl() so subdomain awareness Just Works. External links use <a> + target="_blank" + rel="noopener".<RichText>#
DOMPurify-sanitized HTML renderer.<RichText html={page.body_html} />
Use this instead of dangerouslySetInnerHTML β themes that use raw dangerouslySetInnerHTML will trip the forbidden-script-tag lint rule (and potentially get rejected at admin review).<Form>#
Server-action-style form with progressive enhancement.<Form
action="/api/customer/login"
method="POST"
onSuccess={(data) => router.push("/account")}
onError={(err) => setError(err.message)}
>
<input name="email" type="email" required />
<input name="password" type="password" required />
<button type="submit">Sign in</button>
</Form>
Handles CSRF + idempotency-key + serializes form data β JSON automatically. The form's submit button gets aria-busy while in flight.E-commerce components#
<AddToCartButton>#
Variant-aware, with built-in UX state machine.<AddToCartButton
product={product}
variant={selectedVariant} // optional β defaults to default variant
quantity={qty}
label="Add to bag"
loadingLabel="Addingβ¦"
soldOutLabel="Sold out"
errorLabel="Try again"
onAdded={(p, v) => track("add_to_cart", { product_id: p.id })}
className="theme-btn-primary"
/>
States: idle β adding β idle (or β error for 2s, then idle). When sold-out, renders disabled with soldOutLabel. Wraps useCart().addItem() so themes don't need to reimplement the loading + error UX.<ProductCard> / <CollectionCard>#
Opinionated default cards. Themes that don't want the defaults can ignore them and render their own. Themes that want a quick start can use them straight:<ProductCard product={p} />
<CollectionCard collection={c} />
Each respects the current locale + currency + RTL.<CurrencySwitcher> / <LocaleSwitcher>#
<LocaleSwitcher /> // dropdown of supported locales
<CurrencySwitcher /> // dropdown of presentment currencies
Both update cookies + reload the page. Hidden when only one option is available.<CookieConsent>#
<CookieConsent
text="We use cookies to improve your experience."
acceptLabel="Accept"
denyLabel="Deny"
storageKey="numu_cookie_consent"
/>
Stores the preference in localStorage. Until the banner is dismissed, analytics dispatch is paused.Internals (rarely needed in themes)#
| Export | Purpose |
|---|
ShopContext, CartContext, CustomerContext, etc. | Raw context objects β escape hatches for advanced patterns |
registerSdkSingleton / getSdkSingleton | Federation runtime singletons |
registerReactSingleton / getReactSingleton | React identity registration |
isSdkAvailable() | Check the SDK has loaded (for error boundaries) |
resolveThemeSettings(raw) | Normalize legacy customizer field shapes |
sanitizeHtml(html) | Same DOMPurify config <RichText> uses |
assetUrl(path) | Resolve a theme asset URL |
Avoid these unless you have a specific need β they exist for the SDK's own infrastructure and may change without semver bumps.Patterns#
Loading states#
The Optional hook pattern returns null on miss. Cards that might render outside a context should branch:function MaybeProductCard() {
const product = useProductOptional();
if (!product) return null;
return <ProductCard product={product} />;
}
Async data#
useProducts, useCollections, useOrders, etc. expose { items, loading, error }. Render a skeleton during loading, an error UI on error, and the data on success:const { items, loading, error } = useProducts({ limit: 8 });
if (loading) return <ProductGridSkeleton count={8} />;
if (error) return <div>Couldn't load products.</div>;
return <div className="grid">{items.map(p => <ProductCard key={p.id} product={p} />)}</div>;
Error boundaries#
<Section> and <Block> ship with their own boundaries. For top-level page errors, wrap your route component in a custom boundary:import { ErrorBoundary } from "react-error-boundary";
export default function ProductPage() {
return (
<ErrorBoundary fallback={<div>Something went wrong.</div>}>
<Product />
</ErrorBoundary>
);
}
The SDK doesn't export an <ErrorBoundary> β use the community react-error-boundary package (it's small enough that themes bundle it). Modified atΒ 2026-09-19 15:52:25