Passing JSX Component as Prop

The project is a vanilla React + MUI Joy + Builder app. No pages or features created, currently in the registering components stage.

MUI Joy has a Select component that works like this:

<Select
  placeholder="Select a pet…"
  startDecorator={<FavoriteBorder />}
  endDecorator={
    <Chip size="sm" color="danger" variant="soft">
      +5
    </Chip>
  }
>
  <Option value="dog">Dog</Option>
  <Option value="cat">Cat</Option>
  <Option value="fish">Fish</Option>
  <Option value="bird">Bird</Option>
</Select>

Notice the startDecorator and endDecorator props, they should receive React components.

I am trying to use react-icons (or any icon library) and no method that I’ve tried seems to work.

All icons of a specific package are importer into Builder as custom components:

import * as ReactIcons from "react-icons/ai";

Object.keys(ReactIcons).forEach((iconName) => {
  const Icon = ReactIcons[iconName];
  Builder.registerComponent(
    (props: any) => <Icon {...props} />,
    {
      name: 'Icon' + iconName,
      inputs: [],
    },
  );
});

They appear inside Builder (although I suggest only importing 2-3 for testing), registeringComponents works flawlessly. However there’s no way to have Builder pass an icon as a component to the Select’s startDecorator prop.

These are all the solutions I could think of:

  • Adding it as an input. Not possible as uiBlocks can only be a sub-child of a list, etc.
  • Creating a symbol for each icon. Not possible as symbols need to be created manually, swapping an icon library should take 2 minutes.
  • Creating a Section model that has a Blocks type field. Create an item with an icon as value for the Blocks field. Use that as data in the page and pass it to the Select, doesn’t work.
  • Editing the JSX to pass the Icon myself. I would prefer this, importing the icon directly in JSX from @components and passing it. For some reason Builder removes any icon import when saving the JSX without removing any other components from the import. I realized this is because it thinks the component is not being used, which after many tries seems true, Builder is only passing strings/primitive values to the props. I added a simple string in JSX as the value of the startDecorator prop and it showed up instantly.

This means Builder is not able to pass/insert components as props, only children?

Would a custom plugin solve this in any way?

I feel like <BuilderBlocks> could work for this but not getting any results as of now.

This is the JSX Editor for the Select component. Seems like it’s not capable of sending the component as a prop, but if I try as a child (of Option) it works.

The first line is import { Select, Option, IconAiFillAlert } from @components.

@OmPrakash @manish-sharma @steve @ancheetah

Guys, please don’t tell me I have to switch to Quarkly to be able to use component props and similar stuff.

Builder has pretty much everything I need, but the control over code in Quarkly (which gives the ability to pass any component to any prop directly in the Code tab of the UI Builder) is out of this world.

oh yeah you want BuilderBlocks for this - see this section of our docs for more

JSX editor may not support this, but that’s purely a limitation of that editing mode currently, this technique is used a lot in Builder broadly

1 Like

Thank you @steve , I managed to make it work with BuilderBlocks before you answered.

The limitation I am running into is the fact that I can only add one component type (Text, Select, etc). In my case I used an Icon type component and added it as the default child for a blocks input that connects to the startDecorator prop.

The issue is that I can only have Icon type components as the startDecorator now. There is no possibility to insert any component from the library because once I remove the defaultChildren of the Select component there is no way on the UI to see that slot or add something to it.

The only thing I see on the UI is a path like string as the startDecorator, if I set the input to be visible in Builder. There is no ‘Edit’ button or option to add a component from the library.

For example, I’d like to have a Chip as the startDecorator in some cases. How do I achieve arbitrary component types as BuilderBlocks?

Appreciate your time!

Hi @fijay71018,

If you’d be able to provide some code or snippets of how you’re currently implementing or how you’d like to implement, I may be able to provide better direction.

Basically I have registered an Icon component as the defaultChildren of the startDecorator prop (inside the Select component). I am displaying it using a blocks field (startDecorator) and a BuilderBlocks component to receive the value of the field.

The Select now has an Icon as its startDecorator, great.

Is there a way to have any registered component selectable as the value for startDecorator? If I delete the defaultChildren from the Select component there is no way to add a value to the prop from the Builder editor.

All the code is in the uploaded images above.

@JuliusGD @steve

I’m attaching a video describing the issues that I’m encountering. Basically, I can see the “Add Blocks” button appear successfully but there’s no way for me to tap it, and when I edit the z-index (weird hack) it adds the component outside of the tree.

Importing MUI Option


import { Option } from "@mui/joy";
import * as React from 'react'

const component = (props: any) => {
    const { children, ...rest } = props;

    return (<Option {...rest}>{children}</Option>);
}

const config: any  = {
    name: "Option",
    canHaveChildren: true,
    inputs: [
        {
            name: "value",
            type: "string",
        },
        {
            name: "disabled",
            type: "boolean",
        },
        {
            name: "selected",
            type: "boolean",
        },
    ],
    defaultChildren: [
        {
            "@type": "@builder.io/sdk:Element",
            component: { name: "Text", options: { text: "Option 1" } },
        },
    ],
}

export default {
    component, 
    ...config 
}

Importing MUI Select

import { Blocks } from "@builder.io/sdk-react";
import { Option, Select } from "@mui/joy";
import * as React from 'react'

const component = (props: any) => {
  const { children, startDecorator, endDecorator, ...rest } = props;
  
  return (
    <Select
      {...rest}
      startDecorator={<Blocks blocks={startDecorator}></Blocks>}
      endDecorator={<Blocks blocks={endDecorator}></Blocks>}
    >
      {children}
    </Select>
  );
}

const config: any = {
  name: "Selector",
  noWrap: true,
  canHaveChildren: true,
  inputs: [
    {
      name: 'startDecorator',
      type: 'blocks', // Specify type of blocks
      hideFromUI: false,
      helperText: 'This is an editable region.',
      defaultValue: [
        {
          '@type': '@builder.io/sdk:Element',
          component: {
            name: 'Icon',
          },
          responsiveStyles: {
            large: {
              // Styles for the editable section
            },
          },
        },
      ],
    },
    {
      name: "variant",
      type: "text",
      enum: ["solid", "soft", "outlined", "plain"],
    }
  ],
  defaultChildren: [
    {
      "@type": "@builder.io/sdk:Element",
      component: {
        name: "Option",
        options: {
          value: "opt-1",
        },
      },
      children: [
        {
          "@type": "@builder.io/sdk:Element",
          component: { name: "Text", options: { text: "Option 1" } },
        },
      ],
    },
    {
      "@type": "@builder.io/sdk:Element",
      component: {
        name: "Option",
        options: {
          value: "opt-2",
        },
      },
      children: [
        {
          "@type": "@builder.io/sdk:Element",
          component: { name: "Text", options: { text: "Option 2" } },
        },
      ],
    },
    {
      "@type": "@builder.io/sdk:Element",
      component: {
        name: "Option",
        options: {
          value: "opt-3",
        },
      },
      children: [
        {
          "@type": "@builder.io/sdk:Element",
          component: { name: "Text", options: { text: "Option 3" } },
        },
      ],
    },
  ],
}

export default {
 component,
  ...config
}

I also tried running the components through this code snippet:

const prepareForBuilder = (Component: React.ElementType) => (props: any) => {
  let customProps = { ...props };
  delete customProps.builderBlock;
  delete customProps.builderState;
  delete customProps.builderContext;
  delete customProps.attributes;

  const sxStyle = {
    ...props.builderBlock.responsiveStyles?.[
    props?.builderState?.state?.deviceSize ?? "large"
    ],
  };

  return (
    <Component
      sx={sxStyle}
      {...customProps}
      {...props.attributes}
      // https://forum.builder.io/t/custom-component-with-nowrap-true-cause-focus-issue/817/5
      ref={(el: HTMLElement | null) => {
        props.attributes &&
          Object.entries(props.attributes).forEach(
            ([attributeName, attributeValue]) => {
              if (attributeName.startsWith("builder-")) {
                el?.setAttribute(attributeName, attributeValue as string); // Here i set the "builder-*" attributes on the element
              }
            }
          );
      }} />
  );
};

Any ideas?

Hello filjay71018,

Thanks for the video and outlining the issue.
Would you be able to share your Public API Key?
I’d like to see if I can recreate on my end.

This is the API key: bc3e77058e774dc699a66056c2c6a82f.

Notes

  • I am using SDK v2 (no change with v1, tried that first)
  • I need noWrap to be true, otherwise the CSS changes in the Builder UI do not get applied to the MUI Joy components. I tried various configs and you can see that in the code.

Also uploded the code here: Wormhole - Simple, private file sharing

Thank you, Julius!

Hello fijay71018,

Unfortunately, the link provided sends me to a ‘page not found’.

@JuliusGD

Does this work? Wormhole - Simple, private file sharing

Is there any chance of making this work or should I switch to something else?

Getting no clear details and have no steps remaining to do.

I can’t even add an Icon component if I don’t add it to defaultChildren, as there’s no way to select the prop input from the UI, and when “Add Block” is visible, it can’t be clicked, or adds elements in the wrong tree.

Hi fijay71018,

I was able to reproduce the error with the code you’ve shared. Please allow me some time to take a look.
I hope to come back with some steps forward, soon.

1 Like

Hi fijay71017,

Would you be able to record a screen capture of what you’re experiencing. What I had thought was the error turned out not to be what you were relating to me.

Free screen record service:

Hi Julius

Here is the video again: Video Upload & Annotation - Gemoo

I think it would be better to go from what we’d like to achieve, namely a way to specify that MUI Selector takes in a component/block as startDecorator, and be able to click the “Add Block” button in the UI to add components to startDecorator.

I can’t even add a Box component (to which I would be able to add any child afterwards in the UI), because the Box component that comes out from defaultChildren is different from the one created in the UI. I thought that was very weird, you can see that from their thumbnail in the Layers view.

One has a React icon the other has a simple icon, even though it’s the same Builder Box component. I have no custom Box component, it should be the exact same component in both cases.

This bug shuts down all remaining methods of adding custom children to Builder components.

Any luck with it @JuliusGD?

Hello fijay71018,

Apologies for the delayed response. We did have a U.S. Holiday.

So far, I’m unable to recreate the issue on my end.
Thank you for sharing the demonstration video. Would you be able to share any error logs that appear in the browser on visual editor load.

In the meantime, I will be revisiting the code you’ve shared with me.