← Back to all how-tos

Create a brick

Nesting bricks with Repeater

Nest LinkButton bricks inside a TextImage brick using the React Bricks Repeater component.

Estimated time: 5 minRaw Markdown

What you'll build

In this how-to, you'll nest LinkButton bricks inside a TextImage brick using the React Bricks <Repeater> component.

The goal is to render a row of buttons below the text part of TextImage.

We'll do it in 2 steps:

  • first, prepare the LinkButton brick so it can be reused inside another brick
  • then add a Repeater to TextImage

The starting point

Suppose you already have:

  • a TextImage brick, like the one used in the first how-tos
  • a LinkButton brick, like the one used in the latest guides

Now we want TextImage to render repeated buttons below its rich text.

Step 1: prepare LinkButton

Before using LinkButton inside another brick, we need 2 small changes.

Export the props interface

First, export the props interface:

export interface ButtonProps {
  buttonText: types.TextValue
  path: string
  type?: 'primary' | 'secondary'
}

This is useful because TextImage will import that interface for correct typing.

Hide the brick from the add menu

Now set hideFromAddMenu: true in the schema:

LinkButton.schema = {
  name: 'ws-button',
  label: 'Button',
  hideFromAddMenu: true,
  // ...
}

We do this because the button is not meant to be added directly to a page anymore.

Instead, it will be added only inside other bricks through a repeater.

The updated LinkButton

Your LinkButton.tsx file should now look like this:

import { types, Link, Text } from 'react-bricks/rsc'
import clsx from 'clsx'
 
export interface ButtonProps {
  buttonText: types.TextValue
  path: string
  type?: 'primary' | 'secondary'
}
 
const LinkButton: types.Brick<ButtonProps> = ({
  buttonText,
  path,
  type = 'primary',
}) => (
  <Link
    href={path}
    className={clsx(
      'inline-block text-center font-semibold leading-6 transition-colors rounded-full px-8 py-3 border',
      type === 'primary'
        ? 'text-white bg-indigo-500 hover:bg-indigo-500/90 border-indigo-500'
        : 'text-indigo-500 bg-indigo-50 hover:bg-indigo-50/50 border-indigo-50 hover:border-indigo-200'
    )}
  >
    <Text
      propName="buttonText"
      value={buttonText}
      placeholder="Type an action..."
      renderBlock={({ children }) => <span>{children}</span>}
    />
  </Link>
)
 
LinkButton.schema = {
  name: 'ws-button',
  label: 'Button',
  hideFromAddMenu: true,
  getDefaultProps: () => ({
    buttonText: 'Click me',
    type: 'primary',
    path: '/',
  }),
  sideEditProps: [
    {
      name: 'path',
      label: 'Path',
      type: types.SideEditPropType.Text,
    },
    {
      name: 'type',
      label: 'Type',
      type: types.SideEditPropType.Select,
      selectOptions: {
        display: types.OptionsDisplay.Radio,
        options: [
          { value: 'primary', label: 'Primary' },
          { value: 'secondary', label: 'Secondary' },
        ],
      },
    },
  ],
}
 
export default LinkButton

Step 2: update TextImage

Now let's move to the TextImage brick.

First, import Repeater and ButtonProps.

If you're using Next.js with the App Router, import Repeater from react-bricks/rsc. If you're using Astro, import it from react-bricks/astro.

For the button props, import the exported interface from your LinkButton file.

import { Image, Link, Repeater, RichText, Text, types } from 'react-bricks/rsc'
import type { ButtonProps } from './LinkButton'

Add the buttons prop

Now update the TextImage interface:

interface TextImageProps {
  title: types.TextValue
  description: types.TextValue
  image: types.IImageSource
  buttons: types.RepeaterItems<ButtonProps>
}

types.RepeaterItems<ButtonProps> means that this prop contains repeated items whose props match the ButtonProps interface.

Then read buttons from the component props:

const TextImage: types.Brick<TextImageProps> = ({
  title,
  description,
  image,
  buttons,
}) => {

Add the Repeater

Below the RichText, add a Repeater:

<div>
  <Text
    propName="title"
    value={title}
    placeholder="Type a title..."
    renderBlock={({ children }) => (
      <h2 className="text-3xl font-extrabold">{children}</h2>
    )}
  />
 
  <RichText
    propName="description"
    value={description}
    placeholder="Type a description..."
    renderBlock={({ children }) => (
      <p className="text-lg text-slate-600">{children}</p>
    )}
    allowedFeatures={[types.RichTextFeatures.Bold, types.RichTextFeatures.Link]}
  />
 
  <Repeater
    propName="buttons"
    items={buttons}
    renderWrapper={(items) => <div className="flex gap-4 mt-6">{items}</div>}
  />
</div>

Explain the Repeater props

Here is what each prop does:

  • propName="buttons" binds the repeater to the buttons prop
  • items={buttons} passes the current repeater items from the brick props
  • renderWrapper tells React Bricks how to render the container around the repeated items

What renderWrapper does

The renderWrapper function receives the rendered repeated items and lets you decide how they should be wrapped in the layout.

The wrapper is rendered only when there is at least one repeated item.

In this example:

renderWrapper={(items) => <div className="flex gap-4 mt-6">{items}</div>}

we wrap all repeated buttons inside a div with:

  • flex to place them in a row
  • gap-4 to add spacing between buttons
  • mt-6 to add space above the button row

So renderWrapper is where you define the layout for the repeated bricks as a group, without rendering an empty wrapper when there are no items.

Add repeaterItems to the schema

The last step is telling React Bricks which brick type is allowed inside this repeater.

Add this to the TextImage schema:

TextImage.schema = {
  name: 'text-image',
  label: 'Text Image',
 
  // ...
 
  repeaterItems: [
    {
      name: 'buttons',
      itemType: 'ws-button',
      max: 2,
    },
  ],
}

Explain repeaterItems

Each object inside repeaterItems configures one repeater field.

Here:

  • name: the name of the repeater prop on the component, in this case buttons
  • itemType: the brick type that can be repeated inside that repeater, in this case ws-button
  • max: the maximum number of repeated items allowed

So this configuration means:

  • the repeater is bound to buttons
  • each repeated item must be a ws-button
  • editors can add up to 2 buttons

An repeaterItems object can also include min to require a minimum number of items.

And more generally, a repeater is not limited to just one brick type: React Bricks also lets you configure repeaters that allow multiple kinds of bricks using the items prop.

For the full reference, see the React Bricks docs: The schema's repeaterItems property

Final TextImage example

Your TextImage.tsx file could look like this:

import { Image, Link, Repeater, RichText, Text, types } from 'react-bricks/rsc'
import type { ButtonProps } from './LinkButton'
 
interface TextImageProps {
  title: types.TextValue
  description: types.TextValue
  image: types.IImageSource
  buttons: types.RepeaterItems<ButtonProps>
}
 
const TextImage: types.Brick<TextImageProps> = ({
  title,
  description,
  image,
  buttons,
}) => {
  return (
    <div className="container">
      <div className="grid lg:grid-cols-2 gap-12">
        <div>
          <Text
            propName="title"
            value={title}
            placeholder="Type a title..."
            renderBlock={({ children }) => (
              <h2 className="text-3xl font-extrabold">{children}</h2>
            )}
          />
 
          <RichText
            propName="description"
            value={description}
            placeholder="Type a description..."
            renderBlock={({ children }) => (
              <p className="text-lg text-slate-600">{children}</p>
            )}
            allowedFeatures={[
              types.RichTextFeatures.Bold,
              types.RichTextFeatures.Link,
            ]}
            renderLink={({ children, href, target, rel }) => (
              <Link
                href={href}
                target={target}
                rel={rel}
                className="text-sky-500 hover:text-sky-600 transition-colors"
              >
                {children}
              </Link>
            )}
          />
 
          <Repeater
            propName="buttons"
            items={buttons}
            renderWrapper={(items) => (
              <div className="flex gap-4 mt-6">{items}</div>
            )}
          />
        </div>
 
        <div>
          <Image
            propName="image"
            source={image}
            alt="Image"
            maxWidth={800}
            aspectRatio={16 / 9}
          />
        </div>
      </div>
    </div>
  )
}
 
TextImage.schema = {
  name: 'text-image',
  label: 'Text Image',
 
  getDefaultProps: () => ({
    title: 'Thick as a brick',
    description: 'Another brick in the wall',
  }),
 
  repeaterItems: [
    {
      name: 'buttons',
      itemType: 'ws-button',
      max: 2,
    },
  ],
}
 
export default TextImage

Your TextImage brick now contains nested LinkButton bricks.