How to Add Dynamic Editing Regions to Custom Components

Need to create a custom component with an area where users can drag in additional blocks? This post will guide you through creating one, building off of the <DynamicColumns> component in our react-design-system starter and using <BuilderBlocks> for the editing region.

Read more about the React design system example here

Open the react-design-system example in your code editor and navigate to src/components/. Let’s call our new component CustomColumns and create a folder for it. Add a file called CustomColumns.jsx then copy and paste the code from DynamicColumns.jsx . To make the <CardContent> area editable, replace the text component with <BuilderBlocks/> :

import { Image, BuilderBlocks } from '@builder.io/react';

...

    <CardContent>
        <BuilderBlocks
            key={index}
            child
            parentElementId={props.builderBlock && props.builderBlock.id}
            blocks={col.blocks}
            dataPath={`component.options.columns.${index}.blocks`}
        />
    </CardContent>

...

This is similar to how we implement fully customizable columns in Builder’s built-in Columns component.

Next we need to register the component. Create another file in CustomColumns/ called CustomColumns.builder.js and copy and paste the code from DynamicColumns.builder.js . Notice the list input type here which is what allows users to add more columns identical to the default column. Replace the default text inputs with blocks to make the area under the image dynamically editable.

import { Builder } from '@builder.io/react';
import { CustomColumns } from './CustomColumns';

Builder.registerComponent(CustomColumns, {
  name: 'Custom Columns',
  description: 'Example of a custom column with editing regions',
  inputs: [
    {
      name: 'columns',
      type: 'array',
      defaultValue: [
        {
          image: 'https://cdn.builder.io/api/v1/image/assets%2Fpwgjf0RoYWbdnJSbpBAjXNRMe9F2%2Ffb27a7c790324294af8be1c35fe30f4d',
          blocks: [],
        },
        {
          image: 'https://cdn.builder.io/api/v1/image/assets%2Fpwgjf0RoYWbdnJSbpBAjXNRMe9F2%2Ffb27a7c790324294af8be1c35fe30f4d',
          blocks: [],
        },
      ],
      subFields: [
        {
          name: 'image',
          type: 'file',
          allowedFileTypes: ['jpeg', 'jpg', 'png', 'svg'],
          required: true,
          defaultValue:
            'https://cdn.builder.io/api/v1/image/assets%2Fpwgjf0RoYWbdnJSbpBAjXNRMe9F2%2Ffb27a7c790324294af8be1c35fe30f4d',
        },
				{
          name: 'blocks',
          type: 'blocks',
          hideFromUI: true,
          helperText: 'This is an editable region where you can drag and drop blocks.',
        },
      ],
    },
  ],
});

Now we can test our new Custom Columns component in the visual editor and see that the area under the image is editable!

Additionally, we can add a default component to the blocks as a placeholder:

...
{
    image:
        'https://cdn.builder.io/api/v1/image/assets%2Fpwgjf0RoYWbdnJSbpBAjXNRMe9F2%2Ffb27a7c790324294af8be1c35fe30f4d',
    blocks: [
        {
            '@type': '@builder.io/sdk:Element',
            component: {
                name: 'Text',
                options: {
                    text: 'Enter some text...',
                },
            },
        },
    ],
},
...

custom-cols (1)

View the full code for the Custom Columns component here:

https://github.com/BuilderIO/builder/tree/main/examples/react-design-system/src/components/CustomColumns

1 Like

Just to add on - a simple example that just needs one set of children:

import { BuilderBlocks, Builder } from '@builder.io/react';

function CustomSection(props) {
  return (
    <div>
      <BuilderBlocks blocks={props.children} parentElementId={props.builderBlock.id} dataPath="children" />
    </div>
  );
}

Builder.registerComponent(CustomSection, {
  name: 'My Section',
  inputs: [/* optional, any other inputs */],
  // Optional, if you want default children. If none are provided or if children are empty the
  // "+ add block" button shows
  defaultChildren: [
    {
      '@type': '@builder.io/sdk:Element',
      component: {
        name: 'Text',
        options: {
          text: 'I am a default child!',
        },
      },
    }
  ]
});

Hi Steve, when I use your simple example and try to drop or add a new block using the " + Add block" button these blocks are being added after the Section Block and not inside.

Hey @luigiveoworld - can you share a reproducible code example? Will help us be able to point out any possible issues or replicate on our end to debug

I directly copy and pasted this code block, after that I used it in Builder visual editor and using the “Add block” button is adding the new block after this block and not inside of it.:

import { BuilderBlocks, Builder } from '@builder.io/react';

function CustomSection(props) {
  return (
    <div>
      <BuilderBlocks blocks={props.children} parentElementId={props.builderBlock.id} dataPath="children" />
    </div>
  );
}

Builder.registerComponent(CustomSection, {
  name: 'My Section',
  inputs: [],
  defaultChildren: []
});

hey @luigiveoworld - thanks for sending. On second look I may have gotten a couple important nuances wrong on my first suggestion, I think what we want is this - can you give it a try?

import { BuilderBlocks, Builder } from '@builder.io/react';

function CustomSection(props) {
  return (
    <div>
      <BuilderBlocks blocks={props.builderBlock.children} parentElementId={props.builderBlock.id} dataPath="this.children" />
    </div>
  );
}

Builder.registerComponent(CustomSection, {
  name: 'My Section',
  canHaveChildren: true
});

It’s based on this example builder/Mutation.tsx at main · BuilderIO/builder · GitHub

Thank you @steve I can confirm that it works as expected now.

I have a component with two columns and each column need to receive a block, this code is working in parts because when I add child component in a column in the visual editor, it’s adding the child component in the two columns automaticaly, but I need to add separated, how to do it?

example code:

import React, { useState } from ‘react’
import { Container, Column } from ‘./styles’;
import { BuilderBlocks } from ‘@builder.io/react’;

type Props = {
left?: boolean;
right?: boolean;
builderBlock?: any;
};

function TestComponent({ left, right, builderBlock }: Props) {

return (


{ }

  <Column right={right}>
    { <BuilderBlocks blocks={builderBlock.children} parentElementId={builderBlock.id} dataPath="this.children" /> }
  </Column>

</Container>

)
}

export default TestComponent

I can’t use left or right props because these props are used to define which column will be largest.

Hi @Luiz you can name the props whatever you like, here is an example with two builder components that will take separate content, try it out and adjust the styling, inputs, etc to meet your needs !

import { Builder, withChildren, BuilderBlocks  } from '@builder.io/react';

export const ExampleWithChildren = (props) => {
  return (
    <div className="flex">
        <BuilderBlocks
            child
            parentElementId={props.builderBlock && props.builderBlock.id}
            blocks={props.leftSection}
            dataPath={`component.options.firstSectionWhatever`} />
        <BuilderBlocks
            child
            parentElementId={props.builderBlock && props.builderBlock.id}
            blocks={props.rightSection}
            dataPath={`component.options.thisOtherThing`} />
    </div>
  )
 };

 Builder.registerComponent(withChildren(ExampleWithChildren), {
    name: "exampleWithChildren",
    inputs: [
      {
        name: "leftSection",
        type: "blocks",
        hideFromUI: true,
        defaultValue: [],
      },
      {
        name: "rightSection",
        type: "blocks",
        hideFromUI: true,
        defaultValue: [],
      },
    ],
 })

Hey @TimG ,

I tried using your example on the Builder playground. I’m experiencing an issue where child components are added outside the ExampleWithChildren component. I created a short screencast to demonstrate the issue Screen Recording 2024-02-16 at 1.03.18 PM.mov - Droplr

Any ideas how to resolve this? We have the same issue on any custom component with children we create.

Thanks!

this issue happens when you do not correctly set the default data + <BuilderBlocs /> component parameters.

  • first make sure your input responsible for the blocks contain an empty array, like so:
{
      name: 'includedChildren',
      type: 'array',
      hideFromUI: true,
      subFields: [
        {
          name: 'item',
          type: 'array',
        },
      ],
      defaultValue: [
        {
          item: [],
        },
      ],
    },
  • make sure your builder blocks component have the proper setup (all of the parameters that I used here are necessary):
{includedChildren?.map((child, index) => (
  <BuilderBlocks
     key={index}
     parentElementId={builderBlock?.id}
     dataPath={`component.options.includedChildren.${index}.item`}
     blocks={child.item}d
   />
}