> ## Documentation Index
> Fetch the complete documentation index at: https://docs-loja.usecorvex.com.br/llms.txt
> Use this file to discover all available pages before exploring further.

# Grade de produtos com blocos de catálogo

> Cookbook — card de produto montável no editor (blocos repetidos por item da grade), distinto de snippet fixo ou blocos de página única.

> Quero que o lojista monte o **card do produto** no editor (imagem, título, descrição, preço, botão) e que **cada produto da grade** use a mesma composição. Remover um bloco remove aquele elemento de **todos** os cards.

Referência de implementação: tema de exemplo com seção `main` + blocos `catalog-product-*` + `lib/renderCatalogProductBlocks.jsx`.

Índice: [Cookbooks](https://docs-loja.usecorvex.com.br/cookbook/overview.md)

***

## Runtime Corvex, não motor interno

Grade com blocos nos cards é um caso de uso **suportado de ponta a ponta**: o lojista edita blocos na section; cada produto repete o mesmo molde; preview no builder e vitrine publicada batem.

No código do tema você usa **`@corvex/runtime`** e **`@corvex/theme-sdk`**. O editor entrega o molde em `blocks` e `block_order`. O tema não importa o motor do editor.

Ferramentas usuais para este cookbook:

| Ferramenta | Papel |
| - | - |
| `useSectionBlockTemplate` | Molde `blocks` + `block_order` ao vivo no builder |
| `RepeatedBlockCard` | Card âncora (`editorHost`) + espelho (`renderMirror`) na grade |
| `renderCatalogProductBlocks` (no tema) | Mapa `type` → componente + ordem por produto |
| `useSectionLiveBlocks` | Alternativa de baixo nível se o layout do card for custom |

***

## Quando usar este padrão

| Objetivo | Use |
| - | - |
| Card fixo, rápido, mesmo layout em várias seções | [Product Card](https://docs-loja.usecorvex.com.br/cookbook/product-card.md) (**snippet**) |
| Um slide / um ícone / um item de FAQ por bloco | [Runtime API — Padrão A](https://docs-loja.usecorvex.com.br/reference/runtime-api.md) (`SectionBlockCanvas` + `{children}`) |
| Marquee ou carrossel montado só de settings, sem card no DOM | [Runtime API — Padrão B](https://docs-loja.usecorvex.com.br/reference/runtime-api.md) (`useSectionLiveBlocks`) |
| **Grade de produtos com card editável bloco a bloco** | **Este cookbook (Padrão C)** — `useSectionBlockTemplate` + `RepeatedBlockCard` |

***

## Três camadas (não misture)

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
Section (main)
  ├── settings     → título da grade, colunas, filtros, coleção
  ├── data / fetch → useCorvexData('products') ou defineSectionData
  └── blocks       → “molde” do card (tipos + settings), repetido por produto

Snippet (product-card)
  └── UI fixa; import @corvex/snippets/product-card; NÃO aparece na árvore de blocos

Block catalog-product-*
  └── Uma “fatia” do card; lê o produto atual via contexto; settings só de apresentação
```

**Snippets não viram blocos sozinhos.** Para card composável, crie **tipos de block** dedicados (ou um block único que importa o snippet internamente — o lojista ainda não desmonta o card no editor).

***

## Contratos utilizados

* [Section](https://docs-loja.usecorvex.com.br/reference/contracts/section.md) — `blocks.json`, props `blocks` / `block_order`
* [Block](https://docs-loja.usecorvex.com.br/reference/contracts/block.md) — `settings`; sem fetch de catálogo
* [Product](https://docs-loja.usecorvex.com.br/reference/contracts/product.md) — campos públicos no card
* [Runtime API](https://docs-loja.usecorvex.com.br/reference/runtime-api.md) — `useSectionBlockTemplate`, `RepeatedBlockCard`, `useEditorPreviewCanvas`, `BlockChrome`
* [Theme defaults](https://docs-loja.usecorvex.com.br/guides/theme-defaults.md) — `templates/*.json` com `blocks` + `block_order`

***

## Anatomia recomendada no tema

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
sections/main/
  section.jsx       # loop de produtos + RepeatedBlockCard no builder
  schema.json
  blocks.json       # tipos permitidos + max_blocks
  styles.css

blocks/catalog-product-image/
  block.jsx         # useCatalogProduct() + Image
  schema.json       # type: catalog-product-image, name: Imagem do produto

blocks/catalog-product-title/
  ...

lib/catalogProductContext.jsx   # Provider por produto na grade
lib/renderCatalogProductBlocks.jsx  # map type → componente + ordem
lib/catalogProduct.js             # helpers (preço, imagem, descrição)
```

Registre cada `catalog-product-*` em `theme.json` → `blocks[]`.

***

## 1. `blocks.json` da section

Um tipo por “fatia” do card. `max_blocks` = quantidade de tipos distintos (não repita o mesmo tipo várias vezes no manifest se cada fatia é única).

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "blocks": [
    { "type": "catalog-product-image" },
    { "type": "catalog-product-title" },
    { "type": "catalog-product-description" },
    { "type": "catalog-product-price" },
    { "type": "catalog-product-button" }
  ],
  "max_blocks": 5
}
```

O rótulo de cada bloco vem de `blocks/{id}/schema.json` → `name`, não deste arquivo.

O build anexa o manifest no componente da section. **Não** importe `blocks.json` no JSX.

***

## 2. Block — receber produto sem fetch

```jsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { BlockChrome } from '@corvex/runtime';
import { useCatalogProduct } from '../../lib/catalogProductContext.jsx';

export default function CatalogProductTitleBlock({ settings = {}, product: productProp }) {
  const product = useCatalogProduct(productProp);
  const title = product?.title || 'Nome do produto';
  // settings: cor, alinhamento, etc.
  return (
    <BlockChrome label="Título do produto">
      <h2>{title}</h2>
    </BlockChrome>
  );
}
```

* O tipo é o id da pasta (`blocks/catalog-product-title/`). Se `schema.json` tiver `type`, use o mesmo id. O validador não exige `type`.
* `name` no schema é o rótulo do bloco no editor. Pode mudar. Não mude a pasta depois que a página já existe.

***

## 3. Section — loop + render repetido

```jsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { useMemo } from 'react';
import {
  RepeatedBlockCard,
  SectionChrome,
  useEditorPreviewCanvas,
  useSectionBlockTemplate,
} from '@corvex/runtime';
import { useCorvexData } from '@corvex/theme-sdk';
import { CatalogProductProvider } from '../../lib/catalogProductContext.jsx';
import {
  renderCatalogProductBlocks,
  resolveCatalogBlocks,
} from '../../lib/renderCatalogProductBlocks.jsx';

export default function MainSection({
  settings = {},
  blocks = {},
  block_order = [],
  children,
}) {
  const inBuilder = useEditorPreviewCanvas();
  const blockTemplate = useSectionBlockTemplate(blocks, block_order);
  const catalogBlocks = useMemo(
    () => resolveCatalogBlocks(blockTemplate.blocks, blockTemplate.block_order),
    [blockTemplate.blocks, blockTemplate.block_order]
  );
  const { data } = useCorvexData('products', { /* filtros da section */ });
  const products = normalizeProducts(data);

  return (
    <SectionChrome settings={settings}>
      <ul>
        {inBuilder && products.length === 0 ? (
          <li>
            <CatalogProductProvider product={null}>
              <RepeatedBlockCard editorHost>{children}</RepeatedBlockCard>
            </CatalogProductProvider>
          </li>
        ) : null}
        {products.map((product, index) => (
          <li key={product.id}>
            <CatalogProductProvider product={product}>
              <RepeatedBlockCard
                editorHost={inBuilder && index === 0}
                renderMirror={() =>
                  renderCatalogProductBlocks(
                    product,
                    catalogBlocks.blocks,
                    catalogBlocks.block_order,
                    { /* ações */ }
                  )
                }
              >
                {children}
              </RepeatedBlockCard>
            </CatalogProductProvider>
          </li>
        ))}
      </ul>
    </SectionChrome>
  );
}
```

Pontos obrigatórios:

1. **Vitrine:** `renderCatalogProductBlocks` (ou equivalente) em **cada** produto — mesma `block_order`.
2. **Builder:** use `RepeatedBlockCard` com `editorHost` no primeiro card (ou no lugar vazio, se não houver produtos) e `renderMirror` nos demais. Não monte os cards à mão a partir de `blocks` no iframe.
3. **Molde ao vivo:** `useSectionBlockTemplate(blocks, block_order)` devolve `blocks` e `block_order` atualizados no builder.
4. **Persistência:** `blocks` e `block_order` nos props da section (template e publicação). O editor atualiza esses props ao editar, reordenar e remover itens.

**Fronteira do tema:** não importe o motor do editor, não declare propriedade estática no componente e não importe código interno da plataforma. A integração com o builder passa por `@corvex/runtime`.

***

## 4. `renderCatalogProductBlocks`

Mapa explícito `type` → componente; itere `block_order`, nunca `Object.keys` sem ordem.

```jsx theme={"theme":{"light":"github-light","dark":"github-dark"}}
const BLOCK_COMPONENTS = {
  'catalog-product-image': CatalogProductImageBlock,
  'catalog-product-title': CatalogProductTitleBlock,
  // ...
};

export function renderCatalogProductBlocks(product, blocks, block_order, actions = {}) {
  const order = block_order?.length ? block_order : Object.keys(blocks || {});
  return order
    .map((id) => {
      const entry = blocks[id];
      const Component = BLOCK_COMPONENTS[entry?.type];
      if (!Component) return null;
      return (
        <Component
          key={`${product.id}-${id}`}
          settings={entry.settings || {}}
          product={product}
          {...actions}
        />
      );
    })
    .filter(Boolean);
}
```

`resolveCatalogBlocks` pode definir defaults quando `blocks` está vazio (primeira instalação).

***

## 5. Persistência e templates

| Camada | Onde | O que guarda |
| - | - | - |
| Theme defaults | `templates/index.json` (etc.) | `sections.main.blocks`, `block_order`, `settings` |
| Merchant | Editor → Publish | Mesma forma no JSON da loja |
| Editor ao vivo | Árvore de blocos da section | Sincronizada em `props.blocks` / `props.block_order` |

**Checklist de template:** se `order` inclui `"main"`, a chave `sections.main` deve existir com `blocks` + `block_order`. Referência: `templates/search.json` no tema de exemplo.

***

## 6. O que fazer / o que não fazer

### Faça

* Um block por fatia do card. O tipo é a pasta (`catalog-product-title`). `schema.type`, se existir, repete esse id.
* Provider de produto **por item** da grade (`CatalogProductProvider`).
* `block_order` como fonte de ordem na vitrine e no editor.
* `useSectionBlockTemplate` + `RepeatedBlockCard` (ou composição equivalente com `useSectionLiveBlocks` + `SectionBlockCanvas` se o layout exigir).
* Defaults completos no template para a seção aparecer correta ao adicionar na página.

### Não faça

* Usar snippet `product-card` na mesma seção e esperar que blocos do editor alterem o snippet.
* Assumir que `product-list` (carrossel com snippet) compartilha blocos com `main`.
* Fetch de produtos dentro do block (`storefront.products.get` no block).
* Montar `{children}` duas vezes na grade visível (ghost blocks).
* Fallback que renderiza 5 imagens quando o tipo do block não resolve — corrija `type` no molde (`useSectionBlockTemplate` / props da section).
* Esconder fatia só com toggle `mostrar: false` **se** a intenção é remover a fatia do card — prefira remover o bloco na árvore (toggle é opcional para o lojista).

***

## 7. Sintomas comuns

| Sintoma | Causa provável |
| - | - |
| Vários blocos viram só imagem | `blocks[id].type` errado (todos `catalog-product-image`); re-adicione blocos após corrigir editor/plataforma |
| Remover título e ficar 2 imagens | Mesmo problema de `type` |
| Mudança na árvore não reflete na grade | Section sem proxy do runtime (`useSectionBlockTemplate` / `RepeatedBlockCard`); atualize o editor e o tema |
| Selecionar bloco mostra label flutuante / fora do card | `{children}` em host oculto; use `RepeatedBlockCard` com `editorHost` no **primeiro card** da grade |
| Blocos não aparecem ao adicionar seção | Template sem `sections.{id}.blocks` |
| Card diferente na home vs busca | Templates diferentes — alinhe defaults |

***

## 8. Checklist antes de publicar o tema

* [ ] `sections/{id}/blocks.json` + ids em `theme.json` → `blocks[]`
* [ ] Cada bloco na pasta `blocks/{id}/`; `name` pode mudar, a pasta não
* [ ] Section com `data` ou `useCorvexData` para produtos
* [ ] `renderCatalogProductBlocks` (ou equivalente) + contexto de produto
* [ ] `RepeatedBlockCard` (ou equivalente documentado) no builder
* [ ] Templates com `blocks` + `block_order` para a section de grade
* [ ] `corvex validate .` e `corvex build .`
* [ ] Teste no editor: adicionar/remover/reordenar blocos e conferir **todos** os cards da grade

***

## Relacionados

* [Product Card (snippet)](https://docs-loja.usecorvex.com.br/cookbook/product-card.md)
* [Lista de produtos (data)](https://docs-loja.usecorvex.com.br/data/sections/product-list.md)
* [Theme defaults](https://docs-loja.usecorvex.com.br/guides/theme-defaults.md)
* [Runtime API](https://docs-loja.usecorvex.com.br/reference/runtime-api.md)
* [Pitfalls](https://docs-loja.usecorvex.com.br/data/pitfalls.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.