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

HomeComponentRenderer

componentAdvancedserverbrowserlive demo

Draws one home page block from its API data: finds its registered component, wraps it in an error boundary, and defers it until visible.

import { HomeComponentRenderer } from '@salla.sa/twilight-theme-engine/components/home';

In plain words

The engine's home page is a loop over the merchant's block list, and this is what draws each entry. For one block it finds the component registered for its path, shows a grey placeholder until the block scrolls near the screen, and keeps a block that crashes from taking the rest of the page down.

You need it only when you build your own home page from the block list.

Signature

const HomeComponentRenderer: React.MemoExoticComponent<(props: {
  data: HomeComponentData;   // one entry of home.queries.components()
  index: number;             // its place in the list, from 0
}) => JSX.Element>

interface HomeComponentData {   // @salla.sa/twilight-theme-engine/types
  path: string;
  key?: string | number | null;
  view_style?: string | null;
  [key: string]: …;
}

Try it live

This store's real home page blocks, one at a time, through HomeComponentRenderer with the engine's default blocks registered.Try this: step through the blocks. Theme-only blocks (brands, main-links…) resolve to nothing: a yellow card in development, an empty space in production.
Storefront canvas · ar · RTL

Loading the home page blocks…

Controls
0 to 2 render at once; from 3 the block waits until it scrolls into view.
What a theme writes
import { useQuery } from '@tanstack/react-query';
import { home } from '@salla.sa/twilight-theme-engine/api/home';
import { HomeComponentRenderer } from '@salla.sa/twilight-theme-engine/components/home';

export function HomeBlocks() {
  const { data = [] } = useQuery(home.queries.components());
  return data.map((block, index) => (
    <HomeComponentRenderer
      key={block.key ?? JSON.stringify(block)}
      // The engine's home loader strips the "home." prefix theme blocks arrive with.
      data={{ ...block, path: block.path.replace('home.', '') }}
      index={index}
    />
  ));
}

Example

app/components/home/HomeBlocks.tsx
import { useQuery } from '@tanstack/react-query';
import { home } from '@salla.sa/twilight-theme-engine/api/home';
import { HomeComponentRenderer } from '@salla.sa/twilight-theme-engine/components/home';

export function HomeBlocks() {
  const { data = [] } = useQuery(home.queries.components());
  return data.map((block, index) => (
    <HomeComponentRenderer
      key={block.key ?? JSON.stringify(block)}
      data={{ ...block, path: block.path.replace('home.', '') }}
      index={index}
    />
  ));
}

How it behaves

  • Lookup: home:<path>:<view_style> when the block has a view_style, then home:<path>, then a fallback that shows a yellow "Unknown component" card in development and nothing in production.

  • The component receives data={{ ...data, position: index + 1, priority: index <= 2 }}, inside ComponentErrorBoundary with position={index + 1}.

  • The wrapper is RenderWhenVisible (from /components/common): blocks with index 0 to 2 render at once; later ones are not in the React tree until they come within 50px of the viewport, so their effects and requests wait too.

  • Wrapper shell per path (reserved height, placeholder, class, id): a config registered with registerHomeComponentConfig, else the built-in one for engine blocks, else s-block s-block--<path>, 400px and the products-slider skeleton.

  • The engine's HomePage renders home:start and home:content slots, then one renderer per block with key={data.key ?? JSON.stringify(data)}, then home:end. Its loader strips home. from each path, which theme blocks arrive with (home.brands).

Gotchas

  • Pass paths without the home. prefix. The API sends theme blocks as home.<name>; given the raw list, the renderer looks up home:home.brands and finds nothing.

  • It logs [HomeComponentRenderer] MOUNT and UNMOUNT with console.log for every block, in production builds too: only the per-render log is limited to development.

  • priority goes inside data. FixedBanner reads a top-level priority prop, so through the renderer a banner at the top of the page is never loaded eagerly.

Related

Source and docs