tsx

Dynamically render blocks in PayloadCMS

The satisfies keyword is perfect here because it validates the object shape against Record<BlockSlug, ComponentType<any>> without widening the type. This means TypeScript will error at compile time if you add a new block type in Payload but forget to add the corresponding component. Unlike a type annotation (: Record<...>), satisfies preserves the narrow literal types of the keys, giving you autocomplete and strict key checking while still enforcing the constraint.

import type { Page } from "@/payload-types"
import type { BlockSlug } from "payload"
import type { ComponentType } from "react"

export function RenderBlocks({ blocks }: { blocks: Page["content"][0][] }) {
  return blocks.map((block, index) => {
    if (!(block.blockType in blockComponents)) {
      return null
    }

    const Block = blockComponents[block.blockType]

    return (
      <div key={index}>
        {/* @ts-expect-error block props should be correct at this point */}
        <Block {...block} />
      </div>
    )
  })
}

const blockComponents = {
  pageTitle: PageTitleBlock,
  sectionHeader: SectionHeaderBlock,
  anchorNavigation: AnchorNavigationBlock,
  textImage: TextImageBlock,
  productCardGrid: ProductCardGridBlock,
  topicTeaser: TopicTeaserBlock,
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
} satisfies Record<BlockSlug, ComponentType<any>>
typescriptreactpayloadcms
lubosmato