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

hookRegistry

objectBeginnerserverbrowserlive demo

The one shared list of slot handlers: register content under a slot name, read or clear it, or register many at once with defineHooks.

import { hookRegistry, defineHooks, HookRegistry } from '@salla.sa/twilight-theme-engine/hooks/HookRegistry';

In plain words

Think of hookRegistry as a notice board. hookRegistry.register('footer:start', () => <p>Hello</p>) pins a small function under a name, and every <HookSlot name="footer:start" /> shows what that function returns. Higher priority numbers come first; the default is 50.

defineHooks({...}) pins several at once from an object whose keys are slot names. HookRegistry is the class the shared board was made from; a theme uses the shared hookRegistry.

Signature

const hookRegistry: HookRegistry;

class HookRegistry {
  register<T = Record<string, unknown>>(name: HookName | string, handler: HookHandler<T>, priority?: number): void;  // priority 50
  getHandlers(name: HookName | string): HookDefinition[];  // highest priority first
  has(name: HookName | string): boolean;
  list(): string[];
  clear(name: HookName | string): void;   // removes every handler under the name
  clearAll(): void;
  subscribe(name: HookName | string, callback: () => void): () => void;
  unsubscribe(name: HookName | string, callback: () => void): void;
}

function defineHooks(hooks: HookHandlers): void;

Try it live

A hook slot on this page, and the registry that fills it. Every handler you register renders.Try this: register the same text twice: registration appends, it never replaces. Then raise the priority of a third one.
Storefront canvas · en · LTR
Runs in the browser…
Controls
Higher runs first. The default is 50.
What a theme writes
import { hookRegistry, HookName } from '@salla.sa/twilight-theme-engine/hooks';

// app/hooks/index.tsx: register once, when the theme starts.
hookRegistry.register(
  HookName.HEADER_START,
  () => <p className="announcement">{'Free delivery over 200 SAR'}</p>,
);

Example

app/hooks/index.tsx
import { defineHooks, hookRegistry } from '@salla.sa/twilight-theme-engine/hooks/HookRegistry';
import { HookName } from '@salla.sa/twilight-theme-engine/types/hooks';
import { AnnouncementBar } from '../components/AnnouncementBar';
import { ChatWidget } from '../components/ChatWidget';

let registered = false;

export function registerThemeHooks() {
  if (registered) return; // register() never de-duplicates
  registered = true;

  hookRegistry.register(HookName.HEADER_START, () => <AnnouncementBar />, 60);

  defineHooks({
    [HookName.FOOTER_END]: () => <small>Delivery across the Gulf</small>,
    [HookName.BODY_END]: [{ handler: () => <ChatWidget />, priority: 10 }],
  });
}

How it behaves

  • register appends and re-sorts by priority, highest first, then notifies the slots showing that name. clear(name) and clearAll() notify them too.

  • There is no way to remove one handler: only clear(name) (everything under the name) or clearAll().

  • The registry lives at module scope: one per browser tab, and one per server process. Registering at module load, as the reference theme does from app/router.tsx, applies to every request and renders in the server HTML.

  • defineHooks loops over register. A plain function is registered at priority 50; an array item uses its own priority, or 50.

  • The engine's own defaults are in this registry too (see registerDefaultHooks).

  • Also exported from @salla.sa/twilight-theme-engine/hooks; both paths share one instance.

Gotchas

  • register never de-duplicates: calling the same registration twice renders the content twice. The reference theme does exactly this: app/hooks/index.tsx calls registerThemeHooks() when it is imported, and app/router.tsx imports it and calls it again. Guard with a flag, as in the example.

  • Register at module load, never while rendering or per request. On the server the registry outlives the request, so a registration made during a render piles up for every later visitor.

  • Registering above a default's priority does not replace it (docs/06-hook-system.md, "Override Defaults", suggests it does): both render. And clear(HookName.BODY_END) also removes the engine's TwilightBundles and fast-checkout script.

  • The JSDoc of defineHooks shows a single { handler, priority } object as a value. TypeScript rejects it; in plain JavaScript that object is registered as if it were a function, fails when the slot calls it, and the slot silently renders nothing. Use a function, or an array of { handler, priority }.

  • A new HookRegistry() is a separate, private board: HookSlot only reads the exported hookRegistry.

Related

Source and docs