Create a brick
Nesting bricks with Repeater
Nest LinkButton bricks inside a TextImage brick using the React Bricks Repeater component.
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
LinkButtonbrick so it can be reused inside another brick - then add a
RepeatertoTextImage
The starting point
Suppose you already have:
- a
TextImagebrick, like the one used in the first how-tos - a
LinkButtonbrick, 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 LinkButtonStep 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 thebuttonspropitems={buttons}passes the current repeater items from the brick propsrenderWrappertells 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:
flexto place them in a rowgap-4to add spacing between buttonsmt-6to 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 casebuttonsitemType: the brick type that can be repeated inside that repeater, in this casews-buttonmax: 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 TextImageYour TextImage brick now contains nested LinkButton bricks.