How to implement editable regions for custom component in React with SDK Gen 2

The document doesn’t have an example on how to implement editable regions with React. I tried with Blocks component in Next.js but it throws error when I add the custom component in Visual Editor.
Here is what the Blocks look like:

<Blocks
	blocks={props.sectionA}
	parent={props.builderBlock.id}
	path='component.options.sectionA'
/>

And the registry:

{
	component: Hero,
	name: 'Hero',
	inputs: [
		{
			name: 'sectionA',
			type: 'uiBlocks',
			hideFromUI: true,
			helperText: 'This is an editable region.',
			defaultValue: [
				{
					'@type': '@builder.io/sdk:Element',
					component: {
						name: 'Text',
						options: {
							text: 'Section A Editable in Builder...',
						},
					},
				},
			],
		},
		{
			name: 'sectionB',
			type: 'uiBlocks',
			hideFromUI: true,
			helperText: 'This is an editable region.',
			defaultValue: [
				{
					'@type': '@builder.io/sdk:Element',
					component: {
						name: 'Text',
						options: {
							text: 'Section B Editable in Builder...',
						},
					},
				},
			],
		},
	],
}

This is the error in Next.js:

Warning: React.jsx: type is invalid – expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it’s defined in, or you might have mixed up default and named imports.

Logging the builderBlock returns this:

{
  "@type": "@builder.io/sdk:Element",
  "@version": 2,
  "id": "builder-3b94ef94ebeb40fbbfcbc1ff4cd1b02c",
  "component": {
    "name": "Hero",
    "options": {
      "sectionA": [
        {
          "@type": "@builder.io/sdk:Element",
          "@version": 2,
          "id": "builder-6ed7e374e928482ca8595c78c2636f0b",
          "component": {
            "name": "Text",
            "options": {
              "text": "Section A Editable in Builder..."
            }
          }
        }
      ],
      "sectionB": [
        {
          "@type": "@builder.io/sdk:Element",
          "@version": 2,
          "id": "builder-41e415a17612478ab11bab96c6ddaad8",
          "component": {
            "name": "Text",
            "options": {
              "text": "Section B Editable in Builder..."
            }
          }
        }
      ]
    }
  },
  "responsiveStyles": {
    "large": {
      "display": "flex",
      "flexDirection": "column",
      "position": "relative",
      "flexShrink": "0",
      "boxSizing": "border-box"
    }
  }
}

Hello @nghia.vi,

You may find help at the below links

I have read those documents thoroughly. I think the problem is the Blocks component itself from @builder.io/sdk-react-nextjs. Please read my question again I’ve included my code and the error.
And as I said, the docs doesn’t include example on how to use the Blocks component in Next.js specifically. I’ve followed the setup with other framework but the error still occur.

What I want to achieve is to have the + Add Block button in my custom component where children is expected. I have a Container component which accept children but when dragged into the Visual Editor the Container doesn’t have default height so it’s very hard for users to add children inside it by drag & drop. Also it’s harder for users to know if a custom component accept children or not. My thought is that Blocks component from @builder.io/sdk-react-nextjs package has some problem since I’ve tried everything, even try Blocks without any props, in RSC and not in RSC, but the error still occur.
I know I’m using an experimental package so my app can break here and there. But there is no where else I could report this

Hi. Is this issue still being investigated? I just want to be able to specify which parts of my custom component can user drag blocks into with SDK Gen 2 (@builder.io/sdk-react-nextjs). No matter what I do the Blocks component always throw error. I’m happy to provide my code for further insights, and if I was doing something wrong I want to know what it is and how can I fix the code to make it works.
Thanks in advance

Hello @nghia.vi,

We are currently investigating this issue. I suspect the problem may be related to the support for @builder.io/sdk-react-nextjs. It would be very helpful for our investigation if you could share the code for your implementation.

Thanks,

Sorry for the late reply.
Here is my CustomColumns implementation:

'use client';

import { Blocks, BuilderBlock } from '@builder.io/sdk-react-nextjs';
import { Box, Card, Grid } from '@mantine/core';
import Image from 'next/image';

import { PropsWithBuilderContext } from '@/types';

interface Column {
	blocks: BuilderBlock[];
	image?: string;
}

interface CustomColumnsProps {
	builderBlock?: BuilderBlock;
	columns?: Column[];
}

export const CustomColumns = ({ builderBlock, columns = [] }: PropsWithBuilderContext<CustomColumnsProps>) => {
	return (
		<Box p={30}>
			<Grid gutter='lg'>
				{columns.slice(0, 4).map((col, index) => (
					<Grid.Col
						key={index}
						miw={300}
						span={{ base: 12, md: 6 }}
					>
						<Card>
							<Card.Section>
								<div className='relative aspect-video'>
									<Image
										alt=''
										className='object-cover'
										fill
										src='/default-image.svg'
									/>
								</div>
							</Card.Section>
							<Blocks
								key={index}
								blocks={col.blocks}
								parent={builderBlock?.id}
								path={`component.options.columns.${index}.blocks`}
							/>
						</Card>
					</Grid.Col>
				))}
			</Grid>
		</Box>
	);
};

this is the registry:

import { RegisteredComponent } from '@builder.io/sdk-react-nextjs';

import { CustomColumns } from '.';

export const CustomColumnsEntry: RegisteredComponent = {
	component: CustomColumns,
	name: 'CustomColumns',
	inputs: [
		{
			name: 'columns',
			type: 'array',
			defaultValue: [
				{
					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...',
								},
							},
							responsiveStyles: {
								large: {
									display: 'flex',
									flexDirection: 'column',
									position: 'relative',
									flexShrink: '0',
									boxSizing: 'border-box',
									marginTop: '20px',
									lineHeight: 'normal',
									height: 'auto',
									textAlign: 'center',
								},
							},
						},
					],
				},
				{
					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...',
								},
							},
							responsiveStyles: {
								large: {
									display: 'flex',
									flexDirection: 'column',
									position: 'relative',
									flexShrink: '0',
									boxSizing: 'border-box',
									marginTop: '20px',
									lineHeight: 'normal',
									height: 'auto',
									textAlign: 'center',
								},
							},
						},
					],
				},
			],
			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.',
					defaultValue: [
						{
							'@type': '@builder.io/sdk:Element',
							component: {
								name: 'Text',
								options: {
									text: 'Enter some text...',
								},
							},
							responsiveStyles: {
								large: {
									display: 'flex',
									flexDirection: 'column',
									position: 'relative',
									flexShrink: '0',
									boxSizing: 'border-box',
									marginTop: '20px',
									lineHeight: 'normal',
									height: 'auto',
									textAlign: 'center',
								},
							},
						},
					],
				},
			],
		},
	],
};

props.builderBlock:

{
  "@type": "@builder.io/sdk:Element",
  "@version": 2,
  "id": "builder-55dd460b4268467a8fbdbc6dd3d324ae",
  "component": {
    "name": "CustomColumns",
    "options": {
      "columns": [
        {
          "image": "https://cdn.builder.io/api/v1/image/assets%2Fpwgjf0RoYWbdnJSbpBAjXNRMe9F2%2Ffb27a7c790324294af8be1c35fe30f4d",
          "blocks": [
            {
              "@type": "@builder.io/sdk:Element",
              "@version": 2,
              "id": "builder-489f9f510068476396d1d60c273eeb16",
              "component": {
                "name": "Text",
                "options": {
                  "text": "Enter some text..."
                }
              },
              "responsiveStyles": {
                "large": {
                  "display": "flex",
                  "flexDirection": "column",
                  "position": "relative",
                  "flexShrink": "0",
                  "boxSizing": "border-box",
                  "marginTop": "20px",
                  "lineHeight": "normal",
                  "height": "auto",
                  "textAlign": "center"
                }
              }
            }
          ]
        },
        {
          "image": "https://cdn.builder.io/api/v1/image/assets%2Fpwgjf0RoYWbdnJSbpBAjXNRMe9F2%2Ffb27a7c790324294af8be1c35fe30f4d",
          "blocks": [
            {
              "@type": "@builder.io/sdk:Element",
              "@version": 2,
              "id": "builder-b875cfec35534ba99f844c782678fb91",
              "component": {
                "name": "Text",
                "options": {
                  "text": "Enter some text..."
                }
              },
              "responsiveStyles": {
                "large": {
                  "display": "flex",
                  "flexDirection": "column",
                  "position": "relative",
                  "flexShrink": "0",
                  "boxSizing": "border-box",
                  "marginTop": "20px",
                  "lineHeight": "normal",
                  "height": "auto",
                  "textAlign": "center"
                }
              }
            }
          ]
        }
      ]
    }
  },
  "responsiveStyles": {
    "large": {
      "display": "flex",
      "flexDirection": "column",
      "position": "relative",
      "flexShrink": "0",
      "boxSizing": "border-box"
    }
  }
}

I’m getting these errors:

Warning: React.jsx: type is invalid – expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it’s defined in, or you might have mixed up default and named imports.
at Ee (webpack-internal:///(ssr)/./node_modules/.pnpm/@builder.io+sdk-react-nextjs@0.14.21_next@14.2.3_@babel+core@7.24.4_react-dom@18.3.1_react@18_phiha7xbsb6cfmiixd7orjunka/node_modules/@builder.io/sdk-react-nextjs/lib/node/USE_CLIENT_BUNDLE-14685e18.js:136:86)

Internal error: Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it’s defined in, or you might have mixed up default and named imports.
at aw (/home/nghiavi/prism-builder/node_modules/.pnpm/next@14.2.3_@babel+core@7.24.4_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/next/dist/compiled/next-server/app-page.runtime.dev.js:35:46775)

if I remove the <Blocks /> the component display correctly.

I tried with RSC but the errors look similar, in additional of this error but I don’t know if it’s related:

⨯ TypeError: Cannot read properties of undefined (reading ‘localState’)
at stringify ()
at stringify ()
at stringify ()
at stringify ()
at stringify ()
digest: “3223408045”

RSC version:

import { Blocks, BuilderBlock } from '@builder.io/sdk-react-nextjs';
import { Box, Card, CardSection, Grid, GridCol } from '@mantine/core';
import Image from 'next/image';

import { PropsWithBuilderContext } from '@/types';

interface Column {
	blocks: BuilderBlock[];
	image?: string;
}

interface CustomColumnsProps {
	builderBlock?: BuilderBlock;
	columns?: Column[];
}

export const CustomColumns = ({ builderBlock, columns = [] }: PropsWithBuilderContext<CustomColumnsProps>) => {
	return (
		<Box p={30}>
			<Grid gutter='lg'>
				{columns.slice(0, 4).map((col, index) => (
					<GridCol
						key={index}
						miw={300}
						span={{ base: 12, md: 6 }}
					>
						<Card>
							<CardSection>
								<div className='relative aspect-video'>
									{col.image ? (
										<Image
											alt=''
											className='object-cover'
											fill
											src={col.image ?? '/default-image.svg'}
										/>
									) : null}
								</div>
							</CardSection>
							<Blocks
								key={index}
								blocks={col.blocks}
								parent={builderBlock?.id}
								path={`component.options.columns.${index}.blocks`}
							/>
						</Card>
					</GridCol>
				))}
			</Grid>
		</Box>
	);
};
import { RegisteredComponent } from '@builder.io/sdk-react-nextjs';

import { CustomColumns } from '.';

export const CustomColumnsEntry: RegisteredComponent = {
	component: CustomColumns,
	name: 'CustomColumns',
	isRSC: true,
	inputs: [
		{
			name: 'columns',
			type: 'array',
			defaultValue: [
				{
					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...',
								},
							},
							responsiveStyles: {
								large: {
									display: 'flex',
									flexDirection: 'column',
									position: 'relative',
									flexShrink: '0',
									boxSizing: 'border-box',
									marginTop: '20px',
									lineHeight: 'normal',
									height: 'auto',
									textAlign: 'center',
								},
							},
						},
					],
				},
				{
					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...',
								},
							},
							responsiveStyles: {
								large: {
									display: 'flex',
									flexDirection: 'column',
									position: 'relative',
									flexShrink: '0',
									boxSizing: 'border-box',
									marginTop: '20px',
									lineHeight: 'normal',
									height: 'auto',
									textAlign: 'center',
								},
							},
						},
					],
				},
			],
			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.',
					defaultValue: [
						{
							'@type': '@builder.io/sdk:Element',
							component: {
								name: 'Text',
								options: {
									text: 'Enter some text...',
								},
							},
							responsiveStyles: {
								large: {
									display: 'flex',
									flexDirection: 'column',
									position: 'relative',
									flexShrink: '0',
									boxSizing: 'border-box',
									marginTop: '20px',
									lineHeight: 'normal',
									height: 'auto',
									textAlign: 'center',
								},
							},
						},
					],
				},
			],
		},
	],
};

here is the page.tsx rendering the custom component:

export default async function Page({ params, searchParams }: PageProps) {
	const urlPath = (params.template ? `/${params.template}` : '') + '/' + (params.page?.join('/') ?? '');
	const configuration = await getWebsiteConfiguration({ urlPath });

	if (!isWebsiteConfigurationValid(configuration)) {
		return <MissingBuilderConfigAlert configuration={configuration} />;
	}

	const cmsEndpoint = configuration.cmsEndpoint;
	const stableId = configuration.stableId;

	const pageContent = await fetchOneEntry({
		options: getBuilderOptions('page', searchParams),
		apiKey: PUBLIC_API_KEY,
		model: 'page',
		userAttributes: { urlPath },
		cacheSeconds: 0,
		staleCacheSeconds: 0,
	});

	const canShowContent = !!pageContent || isPreviewing(searchParams) || isEditing(searchParams);

	if (!canShowContent) {
		return <NotFound />;
	}

	const [{ responseData: webConfiguration }, { responseData: stableCustomFields }] = cmsEndpoint
		? await Promise.all([
				fetch(`${cmsEndpoint}/web/configuration`).then(parseResponseBody<ResponseData<WebConfiguration>>),
				fetch(`${cmsEndpoint}/web/stable_custom_field`).then(parseResponseBody<ResponseData<StableCustomField[]>>),
			])
		: [{ responseData: null }, { responseData: null }];

	const customFields = stableCustomFields
		? stableCustomFields.reduce<Record<string, Omit<StableCustomField, 'media'>>>(
				(fields, field) => ({
					...fields,
					[field.keyCode]: field,
				}),
				{},
			)
		: {};

	return (
		<main>
			<Content
				apiKey={PUBLIC_API_KEY}
				content={pageContent}
				customComponents={customComponents}
				linkComponent={Link}
				model='page'
				data={{
					searchParams,
					cmsEndpoint,
					configuration: webConfiguration,
					customFields,
					stableId,
				}}
			/>
		</main>
	);
}

I’ve built the site with CustomColumns code so you can take a look.