Can't override button component

I had a working button component that was able to override the default button for a while, however it randomly stopped working recently and I can’t work out why.

If I use it as a custom component it renders correctly when I drag it into the editor, however if I name it “Core:Button” and set override to true it does not.

Strangely, it displays the "Inside the custom button" string from the examples below inside the button but none of the styling applies nor do the inputs appear in the editor.

When overriding button component:

As a custom component:

Please fill out as many of the following questions as possible so we can help you promptly! If you don’t know how to answer or it does not apply, feel free to remove it or leave it blank.

Builder content link

Builder public api key
42af8af65aa7499b99e3c273c4769b3b

What are you trying to accomplish
Override the default button component

Code stack you are integrating Builder with
Next.js

Reproducible code example

//button component

import React from "react";

export interface ButtonProps {
    variant: "black" | "red" | "green";
    buttonText: string;
    size: "sm" | "md" | "lg";
    url?: string;
}
const Button: React.FC<ButtonProps> = ({ variant, url, size, buttonText }) => {
    const buttonVariants = {
        black: "bg-black text-white",
        red: "bg-red-500 text-white",
        green: "bg-green-500 text-white"
    };

    const buttonSize = {
        sm: "px-4 py-2 text-sm",
        md: "px-6 py-3 text-base",
        lg: "px-8 py-4 text-lg"
    };

    const buttonVariant = buttonVariants[variant];
    const buttonSizeVariant = buttonSize[size];

    return (
        <a href={url || ""}>
            <button className={`${buttonVariant} ${buttonSizeVariant} rounded-full`}>
                {buttonText}
                Inside the custom button
            </button>
        </a>
    );
};

export default Button;



builder registry

//builder-registry.ts

import dynamic from "next/dynamic";
import { builder, RegisteredComponent, withChildren } from "@builder.io/react";


export const customComponents: RegisteredComponent[] = [     {
        component: dynamic(() => import("./components/Button.component")),
        name: "Core:Button",
        override: true,
        inputs: [
            {
                name: "variant",
                type: "string",
                defaultValue: "black",
                enum: [
                    { label: "Black", value: "black" },
                    { label: "Green", value: "green" },
                    { label: "Red", value: "red" }
                ]
            },
            {
                name: "size",
                type: "string",
                defaultValue: "lg",
                enum: ["sm", "md", "lg"]
            },
            {
                name: "buttonText",
                type: "string",
                defaultValue: "Click me"
            },
            {
                name: "url",
                type: "string",
          
            }
        ]
    }],

Hi,

Thank you for reaching out to the Builder.io Forum. My name is Veronika and I am a Customer Engineer at Builder.

It sounds like you need to ensure that both the visual editor and the custom component are properly set up to override the built in button component while making sure that all the styles and inputs are being correctly rendered.

Here are some troubleshooting tips:

  • Make sure that each component in the CustomComponents array is passed correctly to the <Content customComponents={customComponents] /> . Here is a doc that may be helpful.

  • The other thing I am seeing that may be causing the issue in some browsers is nesting a <button> element inside of an <a> tag.

Could you go ahead and try out this code:

import React from "react";

export interface ButtonProps {
  variant: "black" | "red" | "green";
  buttonText: string;
  size: "sm" | "md" | "lg";
  url?: string;
}

const Button: React.FC<ButtonProps> = ({ variant, url, size, buttonText }) => {
  const buttonVariants = {
    black: "bg-black text-white",
    red: "bg-red-500 text-white",
    green: "bg-green-500 text-white"
  };

  const buttonSize = {
    sm: "px-4 py-2 text-sm",
    md: "px-6 py-3 text-base",
    lg: "px-8 py-4 text-lg"
  };

  const buttonVariant = buttonVariants[variant];
  const buttonSizeVariant = buttonSize[size];

  return (
    <a
      href={url || "#"}
      className={`${buttonVariant} ${buttonSizeVariant} rounded-full inline-block text-center`}
      style={{ display: 'inline-block', textDecoration: 'none' }}
    >
      {buttonText}
      <span className="visually-hidden">Inside the custom button</span>
    </a>
  );
};

export default Button;

That way instead of nesting the conflicting elements, you can style the <a> tag to look like a button.

Here is also some helpful documentation about overriding custom components.

This doc about built in component API reference might also be a good one to have handy.

Please let me know if you have any other questions or if I can help in any other way,

Thank you!

Hi,

I had a chance to look through this again and got it to work locally as well;

I think I see the issue here - it looks like the syntax that you are using is aligned with gen2 SDK, but you are importing react with a gen1 sdk, which is this line of code:

import { builder, RegisteredComponent, withChildren } from "@builder.io/react";

In order to fix this issue and get this to work, you’d either need to change that line from gen1 to gen2 which would be:

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


Another solution to this (which I would recommend) would be to use gen1 SDK all together throughout the project if possible - which, in this case, you’d just need to follow this doc and change your syntax to align with the gen1 SDK. We recommend using the gen1 SDK for best practice in the meantime.

If you’re unsure - feel free to toggle between the SDK generations and see the import and syntax differences, which can be found in the docs as well.

Let me know if you have any further questions,

Thank you!

Hi Veronika

Thanks for your reply.

I decided to adjust my project to match the Gen-1 sdk as per the docs for Next.js App router, but now none of my custom components are showing up in builder.

I am getting this message logged in the console for all of my custom components:

“Missing registration for Button, have you included the registration in your bundle?”

I have also tried registering the custom component within the same file as the custom component but the same thing happens.

Here is my updated code:

builder-registry.ts

builder-registry.ts

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

Builder.registerComponent(
    dynamic(() => import("./components/BulletList.component")),
      {  name: "Core:Button",
        override: true,
        inputs: [
            {
                name: "variant",
                type: "string",
                defaultValue: "black",
                enum: [
                    { label: "Black", value: "black" },
                    { label: "Green", value: "green" },
                    { label: "Red", value: "red" }
                ]
            },
            {
                name: "size",
                type: "string",
                defaultValue: "lg",
                enum: ["sm", "md", "lg"]
            },
            {
                name: "buttonText",
                type: "string",
                defaultValue: "Click me"
            },
            {
                name: "url",
                type: "string",
          
            }
        ]
    })

page.tsx

// app/[...page]/page.tsx

import React from "react";
import { builder } from "@builder.io/sdk";
import { RenderBuilderContent } from "@/components/Builder";


builder.init(process.env.NEXT_PUBLIC_BUILDER_API_KEY);

interface PageProps {
    params: {
        page: string[];
    };
}

export default async function Page(props: PageProps) {
    const model = "page";
    const content = await builder
        .get("page", {
            userAttributes: {
                urlPath: "/" + (props?.params?.page?.join("/") || "")
            },
            // Set prerender to false to return JSON instead of HTML
            prerender: false,
        })
        .toPromise();

return (
        <React.Fragment>
            {/* Render the Builder page */}
            <RenderBuilderContent model={model} content={content} />. 
        </React.Fragment>
}

Builder component

// components/Builder.tsx
"use client";

import { ComponentProps } from "react";
import { BuilderComponent, useIsPreviewing } from "@builder.io/react";
import { builder } from "@builder.io/sdk";

import NotFoundPage from "@/app/not-found";

builder.init(process.env.NEXT_PUBLIC_BUILDER_API_KEY);

type BuilderPageProps = ComponentProps<typeof BuilderComponent>;


export function RenderBuilderContent(props: BuilderPageProps) {
    // Call the useIsPreviewing hook to determine if
    // the page is being previewed in Builder

    const isPreviewing = useIsPreviewing();
    // If `content` has a value or the page is being previewed in Builder,
    // render the BuilderComponent with the specified content and model props.

    if (props.content || isPreviewing) {
        return <BuilderComponent {...props} />;
    }

    return <NotFoundPage />;
}

Hi,

Thank you for your reply. I’m glad you were able to adjust to the Gen1 SDK!

The issue you mentioned about custom components not appearing in Builder, along with the console error “Missing registration for Button, have you included the registration in your bundle?” indicates to me that there is some misalignment with the components being registered and used.

Here are a few solutions/ tricks to make sure all your components are getting registered correctly:

To start - Make sure that all your custom component registration is in the correct place (builder-registry.ts) and that the name field matches the name you are using in the Builder Visual Editor. Ensure that the path you’re using to dynamically import the component works as well,

locally, the dynamic import:
dynamic(() => import("./components/BulletList.component")), did not work,
however, I was able to get it working when I created a module with the corresponding name.
dynamic(() => import("./components/BulletList/BulletList")).

Some of the .tsx dynamic imports are a bit tricky, so if your import isn’t working, try to ensure that the path is correct and pointing to the right component.

Another thing that you can do if you see this error, is register the component through the UI - if you go to your localhost and see this image on the bottom right of the screen:
Screenshot 2024-10-04 at 7.09.18 AM

you can click into the image, into the components tab and ensure that your component is registered there.

Screenshot 2024-10-04 at 7.10.15 AM

Screenshot 2024-10-04 at 7.11.30 AM

This will ensure that you see all your components, registered and not, and you can toggle the registration through the UI.

Lastly, I did notice a small syntax error in your page.tsx file - the return parenthesis is not being closed.
Correct code:
return ( <React.Fragment> {/* Render the Builder page */} <RenderBuilderContent model={model} content={content} />. </React.Fragment> ) }

Not sure if that was a copy/paste error but just wanted to point that out as well.

Let me know if you have any more questions,

Thank you!