Skip to content
Twilight React Playground
ثيم رائدaren

ProductContextProvider

providerAdvancedserverbrowserlive demo

A React context for a product: wrap part of the page once, then read the product anywhere inside it without passing it down.

import { ProductContextProvider, useProductContext, ProductContext, ProductContextValue, ProductContextProviderProps } from '@salla.sa/twilight-theme-engine/contexts';

In plain words

Passing product through five layers of components gets tedious. Wrap that part of the page once, <ProductContextProvider value={{ product }}>, and any component inside can call useProductContext() to get { product }.

Outside a provider it returns null, so components that use it must handle that.

Signature

interface ProductContextValue {
  product: Product;
}

interface ProductContextProviderProps {
  children: ReactNode;
  value: ProductContextValue;
}

function ProductContextProvider(props: ProductContextProviderProps): React.ReactElement
function useProductContext(): ProductContextValue | null
const ProductContext: React.Context<ProductContextValue | null>

Try it live

A product from the demo store, handed to a nested component through ProductContextProvider instead of a prop.Try this: turn the provider off: the same component now reads null and has to cope with it.
Storefront canvas · en · LTR
Runs in the browser…
Controls
wrap in ProductContextProvider
What a theme writes
import {
  ProductContextProvider,
  useProductContext,
} from '@salla.sa/twilight-theme-engine/contexts';
import type { Product } from '@salla.sa/twilight-theme-engine/types';

function ProductTitle() {
  const context = useProductContext(); // null without a provider above
  return context ? <h3>{context.product.name}</h3> : null;
}

export function ProductTile({ product }: { product: Product }) {
  return (
    <ProductContextProvider value={{ product }}>
      <ProductTitle />
    </ProductContextProvider>
  );
}

Example

app/components/ProductTile.tsx
import {
  ProductContextProvider,
  useProductContext,
} from '@salla.sa/twilight-theme-engine/contexts';
import type { Product } from '@salla.sa/twilight-theme-engine/types';

function TileTitle() {
  const context = useProductContext();
  return context ? <h3>{context.product.name}</h3> : null;
}

export function ProductTile({ product }: { product: Product }) {
  return (
    <ProductContextProvider value={{ product }}>
      <TileTitle />
    </ProductContextProvider>
  );
}

How it behaves

  • It is a plain React context and nothing in the engine reads it: the engine's product page passes the product as props, and to hook slots through HookSlot's context prop.

  • The provider memoizes its value on value.product, so consumers render again only when a different product object is passed.

  • ProductContext itself is exported for use(ProductContext) and class components.

Gotchas

  • The source comment says HookSlot injects this context into product:* hook handlers. It does not: HookSlot builds the handler context from its own context prop and twilight only (src/hooks/HookSlot.tsx). Pass context={{ product }} to the slot.

Related

Source and docs