HookSlot
A named empty place in the page that renders every handler registered under its name, plus a spot where Salla apps inject content.
import { HookSlot, HookSlotProps, HookContext } from '@salla.sa/twilight-theme-engine/hooks/HookSlot';In plain words
Engine pages leave named empty places in their layout, such as header:start or cart:summary.end. Each one is a <HookSlot name="…" />. Anything registered under that name, with hookRegistry.register, appears there, highest priority first. You can put slots in your own components too.
A component is a function that returns what should appear on the page, and its props are the attributes you pass to it (name, context…). The slot hands each registered function one object: whatever you passed as context, plus twilight, the engine's store, theme and language data.
Salla marketplace apps can also add their own blocks at the same name. See the recipe Add content to a slot.
Signature
function HookSlot(props: HookSlotProps): JSX.Element | null
interface HookSlotProps {
name: HookName | string;
context?: Record<string, unknown>; // merged into what each handler receives
fallback?: React.ReactNode; // shown when no handler is registered
ssr?: boolean; // default false: <salla-hook> only after mount
}
interface HookContext {
twilight: TwilightContextValue; // store, theme, locale, settings, salla…
[key: string]: unknown; // your context
}Try it live
import { HookSlot } from '@salla.sa/twilight-theme-engine/hooks/HookSlot';
export function PromoArea() {
return (
<HookSlot
name="playground:hook-slot"
context={{ badge: 'Free delivery' }}
fallback={<em>{'No handler for this slot'}</em>}
/>
);
}
Example
import { HookSlot, type HookContext } from '@salla.sa/twilight-theme-engine/hooks/HookSlot';
import { hookRegistry } from '@salla.sa/twilight-theme-engine/hooks/HookRegistry';
import type { Product } from '@salla.sa/twilight-theme-engine/types';
interface ExtrasContext extends HookContext {
product?: Product;
}
// Usually in app/hooks/index.tsx: registered once, when the theme starts.
hookRegistry.register('theme:product.extras', ({ product, twilight }: ExtrasContext) =>
product?.is_on_sale ? <p className="badge">On sale at {twilight.store.name}</p> : null
);
export function ProductExtras({ product }: { product: Product }) {
return <HookSlot name="theme:product.extras" context={{ product }} />;
}
How it behaves
Each handler renders in its own small component, keyed by its registry id, and receives
{ ...context, twilight: useTwilight() }. Because it is called while that component renders, a handler may call React hooks, always in the same order.A handler returning
nullorundefinedrenders nothing. A handler that throws is caught and skipped.The slot subscribes to the registry, so a handler registered after the slot mounted appears without its parent re-rendering, and other slots do not re-render.
It also renders Salla's
<salla-hook name>element, where marketplace apps inject content. That element is added only after hydration unlessssris set, because apps change its DOM before React takes over. The engine setsssronly onhead:start,headandhead:end, where nothing is injected.The component's JSDoc asks themes to add
salla-hook{all:unset;display:none}to their global CSS.Every handler runs again whenever the slot re-renders:
contextis rebuilt each time. Keep handlers cheap.Also exported from
@salla.sa/twilight-theme-engine/hooks, the import the reference theme uses.
Gotchas
fallbackshows only when no handler is registered. A handler that returnsnullstill counts, so the fallback stays hidden and the slot is empty.Only an error thrown by the handler function itself is caught. If it returns an element whose component throws while rendering, the error goes to the nearest React error boundary and can take the page section down. The caught errors are logged only when
NODE_ENVisdevelopment, throughwindow.Salla.logger.A key named
twilightin yourcontextis overwritten:twilightis added after your context.It calls
useTwilight(), so rendering a slot outsideTwilightProviderthrows.
Related
The one shared list of slot handlers: register content under a slot name, read or clear it, or register many at once with defineHooks.
HookNameThe predefined slot names as a TypeScript enum, so a typo fails to compile instead of silently filling a slot nobody renders.
useHookRegisters a slot handler for as long as the calling component is on screen, and clears that slot name when it goes away.