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

The provider: data every component can reach

Beginner8 min

Context shares values without passing props; TwilightProvider shares the store, theme and language, and useTwilight reads them.

Almost every component in a store needs the same few facts: which store this is, its colors, the page language. Passing them as props from the top of the page down through every component would be tedious and fragile.

React's answer is context: a value placed once, high in the tree, that any component below can read directly. The component that places it is called a provider.

Context in thirty seconds

The label below sits four components deep. None of the boxes in between passes it anything, yet it follows the value given to the provider at the top.

<ProductGrid>
<ProductCard>
<CardFooter>
<PriceLabel>228.00 SAR · comfortable
Passing props down
// Without context: every level passes size along, even those that never use it.
function ProductGrid({ size }) {
  return <ProductCard size={size} />;
}
function ProductCard({ size }) {
  return <CardFooter size={size} />;
}
function CardFooter({ size }) {
  return <PriceLabel size={size} />;
}
Using context
import { createContext, useContext } from 'react';

const CardSizeContext = createContext('comfortable');

// Put a value at the top…
export function Catalog() {
  return (
    <CardSizeContext.Provider value="compact">
      <ProductGrid />
    </CardSizeContext.Provider>
  );
}

// …and read it anywhere below, without passing it through the middle.
function PriceLabel() {
  const size = useContext(CardSizeContext);
  return <span className={`price price--${size}`}>228.00 SAR</span>;
}

When the provider's value changes, React re-renders the components that read it, wherever they are.

The engine's provider: TwilightProvider

A theme mounts TwilightProvider exactly once, around every page, in app/routes/__root.tsx. useTwilight() reads its whole value; useStore(), useTheme() and useMoney() read parts of it. Below is the value this page was rendered with, from the real demo store.

app/routes/__root.tsx
// app/routes/__root.tsx (excerpt)
<body suppressHydrationWarning>
  <TwilightProvider translations={themeTranslations}>
    <Outlet /> {/* every page of the theme renders here */}
  </TwilightProvider>
  <Scripts />
</body>
The context value this page was rendered with. Pick a field to look inside; everything is the demo store’s real data.Try this: pick settings: it is the whole store settings response, which store and theme come from.
Storefront canvas · ar · RTL
Open the browser console to see the styled log line.
useTwilight() → store: {…} 24 keys
id: 1510890315
ray: 50
logo: "https://cdn.salla.network/salla.com/logo-wide-1.svg"
icon: "https://cdn.salla.network/salla.com/logo-wide-1.svg"
name: "ثيم رائد"
username: "dev-vgckq3fssfhjewwi"
store_country: "SA"
country: "SA"
url: "https://demostore.salla.sa/ar/dev-vgckq3fssfhjewwi/"
settings: {…} 26 keys
meta: {…} 3 keys
scope: null
template: null
contacts: {…} 4 keys
social: {…} 4 keys
description: "<p class="ql-direction-rtl">هذا المتجر التجريبي يتيح لك استكشاف شكل وتصميم المتاجر على منصة <strong>سلة</strong>. تصفّح الأقسام، جرّب تجربة الشراء، واستعرض الم…"
apps: {…} 2 keys
features: Array(18)
is_merchant: false
support_pickup: true
order_instruction: {…} 4 keys
shipping: {…} 3 keys
rating: {…} 2 keys
ratings: Array(2)
Controls
What a theme writes
import { useTwilight } from '@salla.sa/twilight-theme-engine';

export function Example() {
  const { store } = useTwilight();
  return <span>{store.name}</span>;
}
app/components/StoreLine.tsx
import { useTwilight } from '@salla.sa/twilight-theme-engine';
import { useStore } from '@salla.sa/twilight-theme-engine/hooks/useStore';
import { useTheme } from '@salla.sa/twilight-theme-engine/hooks/useTheme';

export function StoreLine() {
  const { locale, dir } = useTwilight(); // the whole context
  const { name } = useStore();          // the store part of it
  const { color } = useTheme();         // the theme part of it
  return (
    <p lang={locale} dir={dir} style={{ color: color.primary }}>
      {name}
    </p>
  );
}

What else it sets up

Sharing data is only part of its job. Around your pages it also:

  • starts the Salla SDK in the browser and keeps logins in step with the server;
  • sets up translations for useTranslation(), with your theme's own texts added;
  • applies the theme to the document: body classes, the merchant's colors and font as CSS variables;
  • wraps every page in the layout, the header and footer (MasterLayout) unless you pass another;
  • renders the toast notifications, the body:start and body:end hook slots, and the snippets of apps the merchant installed;
  • turns clicks on plain links to the store's own pages, such as links inside Salla's web components or a merchant's content, into in-app navigation without a full page reload.
In engine terms
  • Import TwilightProvider and useTwilight from @salla.sa/twilight-theme-engine. Outside a provider, useTwilight() throws "useTwilight must be used within a TwilightProvider" (src/providers/twilight-context.ts).
  • The context value is memoized on isReady, store, theme, currency, config, log and authToken. settings, locale, routeId, location, i18n, dir and extras are getters that read getTwilightContext() when accessed: reading them never subscribes a component to their changes (src/providers/TwilightProvider.tsx).
  • isReady starts true whenever the store settings loaded, so it is already true in the server render. It does not mean the SDK is ready: wait for Salla.onReady() or the theme::ready event for that.
  • Useful props: translations (from virtual:twilight/theme-translations), layout (a component, or false for none; default MasterLayout), toast, addToCartToast and toastMobilePosition, client ({ framework: 'tanstack', interceptLinks: true } by default) and routeClass.
  • Reference: TwilightProvider, useTwilight, and getTwilightContext for loaders and head functions, which run outside React.
Check yourself

A component in a unit test throws useTwilight must be used within a TwilightProvider. Why?