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

Overriding an engine component

Advanced7 min

register, override, resolve: how the registry swaps a component, which names the engine looks up, and the silent no-op.

The engine draws some parts of the store through a registry: a table of names and components. Put your own component under one of those names and the engine draws yours instead, everywhere, without copying a page.

It works for a few names only, and one of them needs two calls. Step through what happens.

app/router.tsxmodule scoperegister()seed, no originaloverride()keeps the originalregistryname → componentEngine lookupsa few names onlyProductCardgetOriginal() !== nullresolve(name)your componentYour componentsame propsComponent name=…your own lookupsDefault cardno originalOther namesheader, footer: no-op

1. register stores a component under a name

The registry is a shared address book: a name on one side, a component on the other. Your theme writes to it once, when it starts, in app/router.tsx. register stores a component under a name.

In engine terms

registry (root export, src/components/ComponentRegistry.ts) is a module-level Map of { component, displayName, original? }, shared by every request the server handles, so register at module scope, never per request or per render. register(name, C) stores { component: C } with no original, replacing whatever was there.

Which names the engine resolves

NameLooked up byWhat your theme calls
product:cardProductCard, in every grid and sliderregister, then override
product:galleryProductGallery, on the product pageregister
home:<path>, home:<path>:<view_style>HomeComponentRenderer, per home blockregisterHomeComponents
account:layout-pendingThe account layout while it loadsregister
Any other nameOnly your own Component or useComponentregister
app/components/MyProductCard.tsx
import { memo } from 'react';
import { registry } from '@salla.sa/twilight-theme-engine';
import type { ProductCardProps } from '@salla.sa/twilight-theme-engine/components/product';
import { Link } from '@salla.sa/twilight-theme-engine/components/common';
import { useMoney } from '@salla.sa/twilight-theme-engine/hooks/useMoney';

// A full replacement: the engine's card is not available to wrap.
export const MyProductCard = memo(function MyProductCard({ product, className }: ProductCardProps) {
  const { format } = useMoney();
  return (
    <article className={['my-card', className].filter(Boolean).join(' ')}>
      <Link to={product.url}>
        <img src={product.image?.url} alt={product.name} width={300} height={300} loading="lazy" />
        <h3>{product.name}</h3>
      </Link>
      <p>{format(product.price)}</p>
    </article>
  );
});

// app/router.tsx, at module scope, in this order:
registry.register('product:card', MyProductCard); // seed the name
registry.override('product:card', MyProductCard); // now getOriginal() is not null

Try the calls

The lab below runs the same calls on a playground name and shows what ProductCard would do with them. Under it, the registry of this tab: the home: names are the playground's home blocks.

Registry calls on a playground key, and what ProductCard would do if the same calls were made on product:card.Try this: switch to "override only": the key resolves to MyCard, but getOriginal is null, so ProductCard keeps its default.
Storefront canvas · en · LTR
Runs in the browser…
Controls
What a theme writes
import { registry } from '@salla.sa/twilight-theme-engine';
import { MyProductCard } from './components/MyProductCard';

// app/router.tsx: at module scope, before createRouter()
registry.register('product:card', MyProductCard);
registry.override('product:card', MyProductCard);

// Where the engine renders <ProductCard product={…} /> (grids, sliders, listings),
// MyProductCard receives the same props, but only after both calls.

Reading the registry…

Why it matters

  • Your override does nothing, with no warning: product:card got override without register, or the name is not one the engine looks up (header, footer, product.card).
  • Cards already on screen keep the old look: each card decides once, when it mounts. Register in app/router.tsx, before createRouter().
  • The page freezes or crashes after overriding the card: your component renders the engine's ProductCard, which renders your component again. Treat the override as a full replacement.
  • You only want to add something next to the card or the header: a hook slot is usually the better tool, and it cannot silently do nothing.
Check yourself

Your theme calls only registry.override('product:card', MyCard). What do product grids show?