Accessing builderState in custom components withChildren

Using Next.js + React.

We wanted to use the BuilderComponent context in custom components rendered as children of other custom components.

// 404.jsx
<BuilderComponent
  model={Builder.previewingModel || 'builder-page'}
  context={{
    callbackFn: () => console.log('hello'),
  }}
/>

The context option for BuilderComponent is documented in the README for the @builder.io/react npm package: builder/packages/react at main · BuilderIO/builder · GitHub

We were rendering children as in the example of HeroWithChildren:

The issue with above, is that the children are not passed builderState.context, it is empty. To replicate the issue, try out this simplified custom component:

function CustomComponent(props) {
  useEffect(() => props.builderState.context.callbackFn())
  return (
    <div>{props.children}</div>
  )
}

It works great, until you nest a custom component that needs access to builderState.context. Then, the props.builderState.context.callbackFn() is undefined and throws an error in the child CustomComponent. The outer still works.

The simplified component tree would look like:

<CustomComponent> <- can access callbackFn()
  <CustomComponent> <- cannot access callbackFn()
    <div>hey</div>
  </CustomComponent>
</CustomComponent>

The solution, is to render children using BuilderBlocks, and also pass the props.builderBlock.children prop, and not the props.children

function CustomComponent(props) {
  useEffect(() => props.builderState.context.callbackFn())
  return (
    <div>
      <BuilderBlocks
        style={{
          display: 'inline',
        }}
        child
        parentElementId={builderBlock?.id}
        dataPath='this.children'
        blocks={builderBlock.children}
      />
    </div>
  )
}
2 Likes

Maybe not the right place to post this, but wanted to share my findings. This would be a good piece to clarify in docs. Perhaps you could document the actual code in Github, as that would provide a good overview of expected props and usage builder/builder-blocks.component.tsx at main · BuilderIO/builder · GitHub

2 Likes

hey @will - you’ll want BuilderStoreContext for this

import { BuilderStoreContext } from '@builder.io/react'

function CustomComponent(props) {
  const builderState = useContext(BuilderStoreContext);

  useEffect(() => {
      builderState.context.callbackFn()
  })

  return ...
}

Examples in our code here as well

ahh - that would work too.

I think the most interesting distinction in my findings is that
props.children and props.builderBlock.children are two different things

ah yes, props.builderBlock.children is the raw JSON representation

if you use withChildren() we will map the JSON to actual React component children that can render directly with {props.children} (vs the JSON would instead need to be {props.builderBlock.children.map(child => <BuilderBlock block={child} />)})

So it’s just for convenience, if you don’t use withChildren we won’t add props.children

The main benefit is with withChildren you get better reuse, like

export function MyComponent() {
  return <div>{props.children}</div>
}

Builder.registerComponent(withChildren(MyComponent), { ... })

You can use it in your code the conventional way

<MyComponent><SomeChild /></MyComponent>

and in Builder as well, without any extra builder specific stuff in your code

Nice - we are using the BuilderBlocks component to provide a better editing experience so have to use builderBlock.children