How to use react-bootstrap with custom components

I created a registration form custom component for my builder.io page and it seems to work in production. My builder.io page with the component looks like this…

This is exactly what I want! The problem is when I open it for editing, it looks like this…

It is pretty clear that the react-bootstrap components are not being defined. The registration grid is made using the Col, Row elements from react-bootstrap. Here is the code for the custom component…

"use client";
import React, { Dispatch, useEffect } from "react";
// hooks forms
import { CompactFormRegister, CompactSignupSchema } from "./CompactRegistrationSchema";
import { useFieldArray, useForm, useWatch } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import {
  BrokerageInfoSchema,
  FormBrokerageInfo,
} from "./BrokerageInfoSchema";
// redux
import { RootState } from "../../redux/store";
import { useDispatch, useSelector } from "react-redux";
import { register } from "../../redux/auth/auth.actions";
// services
import BrokerageService from "../../api/brokerage.service";
// modals
import { UserProfileModel } from "../../shared/model/UserProfile";
import { BrokerageModel } from "../../shared/model/Brokerage";
import { MarketModel } from "../../shared/model/Market";
// const
import { acceptTerms, refByOptions } from "../../constants/Consts";
// utils
import { transformNameAndIdToOption } from "../../utils/DropdownOptions";
import { formatPhoneNumber } from "../../utils/Validations";
import { toTitleCase } from "../../utils/TitleCase";
// ui
import CustomButton from "../../components/Button/CustomButton";
import { FormInputText } from "../../components/FormComponents/FormInputText";
import { FormInputCheckbox } from "../../components/FormComponents/FormInputCheckbox";
import { FormInputDropdown } from "../../components/FormComponents/FormInputDropdown";
import { FormInputAsyncSelect } from "../../components/FormComponents/FormInputAsyncSelect";
import { Col, Container, Form, Modal, Row } from "react-bootstrap";
import { CustomModal } from "../../components/Modal/CustomModal";
import ImageUpload from "../../components/ImageUpload/ImageUpload";
import { Avatar } from "@mui/material";
import { toast } from "react-toastify";
// icons
import { ChevronBarDown, Eye, EyeSlash } from "react-bootstrap-icons";
import { Variables_E } from "../../enums/rl-variables";
import { set, String } from "lodash";
import ToastSpinner from "../../components/ToastSpinner/ToastSpinner";
import { invImageURL } from "../../utils/TextFormat";
import { StoreProvider } from "@/app/StoreProvider";

interface CompactRegistrationFormProps { 
  affiliateId?: number;
  affiliateName?: string;
}

const CompactRegistrationForm: React.FunctionComponent<CompactRegistrationFormProps> = (
  {
    affiliateId,
    affiliateName,
  }
) => {

  const enableUploadLogo = false; // Disabled since it doesn't work.  Assumes customer record already exists.
  const enableUploadHeadShot = false; // Disabled since it doesn't work.  Assumes customer record already exists.
//  const dispatch: Dispatch<any> = useDispatch();

  // const marketList: MarketModel[] = useSelector(
  //   (state: RootState) => state.market.marketList
  // );

  // hook-forms
  const methods = useForm<CompactFormRegister>({
//    resolver: yupResolver(CompactSignupSchema()),
  });

  const { handleSubmit, control, setValue, watch ,getValues} = methods;

  const methodsBrokerage = useForm<FormBrokerageInfo>({
//    resolver: yupResolver(BrokerageInfoSchema),
  });

  const {
    handleSubmit: brokerageHandleSubmit,
    control: brokerageControl,
    setValue: setValueBrokerage,
  } = methodsBrokerage;

  // watch-fields
  const watchFieldArray = watch("test");

  const phoneNumberWatch = useWatch({
    control,
    name: "phone",
  });

  React.useEffect(() => {
    if (phoneNumberWatch && phoneNumberWatch.length > 0) {
      let FPN = formatPhoneNumber(phoneNumberWatch);
      setValue("phone", FPN);
    }
  }, [phoneNumberWatch]);
  // useStates

  const [password, setPassword] = React.useState("password");
  const [passwordConfirm, setPasswordConfirm] = React.useState("password");

  const [selectedIndex, setSelectedIndex] = React.useState();
  const [marketOptions, setMarketOptions] = React.useState<any>([]);
  const [showReferalSource, setShowReferalSource] = React.useState<boolean>(false);
  //brokerage other option toggle modal
  const [showModalOtherBrokerage, setShowModalOtherBrokerage] =
    React.useState(false);
  const toggleModalOtherBrokerage = () => {
    setShowModalOtherBrokerage((v) => !v);
  };
  // upload logo
  const [showUploadHeadShotModal, setShowUploadHeadShotModal] =
    React.useState(false);
  const toggleModalUploadHeadShot = React.useCallback(() => {
    setShowUploadHeadShotModal((v) => !v);
  }, []);

  // upload logo
  const [showUploadLogoModal, setShowUploadLogoModal] = React.useState(false);
  const [marketId, setMarketId] = React.useState<string>("");
  const [brokerage, setBrokerage] = React.useState<any>(null);
  const toggleModalUploadLogo = React.useCallback(() => {
    setShowUploadLogoModal((v) => !v);
  }, []);

  // market dropdown
  // React.useEffect(() => {
  //   if (marketList) {
  //     const marketOptionsDropDown = transformNameAndIdToOption(marketList);
  //     setMarketOptions(marketOptionsDropDown);
  //   }
  // }, [marketList]);

  const handleMarketChange = async (e: any) => {
    setMarketId(e.target.value);
  };

  const handleBrokerChange = async (
    e: any
  ) => {
    if(e.label === "Add New") {
        toggleModalOtherBrokerage();
    } else {
        setBrokerage(e);
    }
  }

  const togglePassword = () => {
    setPassword(password === "password" ? "text" : "password");
  };

  const toggleConfirmPassword = () => {
    setPasswordConfirm(passwordConfirm === "password" ? "text" : "password");
  };

  // submit other brokerage
  const addBroker = (data: BrokerageModel) => {
    return BrokerageService.addBrokerage(data).then(
      async (response: any) => {
        toast.success(toTitleCase("new brokerage added"));
        setBrokerage({
            value: response.data.data.id,
            label: response.data.data.name + " " + response.data.data.email,
        })
        toggleModalOtherBrokerage();
      },
      (error: any) => {
        const message =
          (error.response &&
            error.response.data &&
            error.response.data.message) ||
          error.message ||
          error.toString();

        toast.warning(message, { autoClose: false });
      }
    );
  };

  const onSubmitBrokerage = (data: BrokerageModel) => {
    toast.dismiss();
    data.market_id = marketId;
    addBroker(data);
  };
  const referral_from = useWatch({ control, name: 'referral_from' });
  useEffect(()=>{
    !getValues('referral_from') && setShowReferalSource(false)

  },[referral_from])

  const onSubmit = async (data: CompactFormRegister) => {
    toast.dismiss();
    let brokerageArray = [ brokerage.value ];
    let marketArray = [ marketId ];
    let RegData: UserProfileModel = {
      first_name: data.first_name,
      email: data.username,
      last_name: data.last_name,
      username: data.username,
      phone: data.phone,
      referral_from: 'affiliate',
      referral_source_name: affiliateName,
      referral_source_id: affiliateId,
      password_hash: data.password_hash,
      brokerage: brokerageArray,
      markets: marketArray,
      email_type: "primary",
      email_intent: "",
    };
    let regMessage;
    try {
    const loadingToastId = toast(
        <ToastSpinner message="Processing..." type="loading" />,
        {
        closeButton: false,
        autoClose: false,
        }
    );
//    regMessage = await dispatch(register(RegData));
    regMessage = "success"; // Simulating successful registration for testing purposes
    toast.dismiss(loadingToastId);

    if (regMessage === "success") {
        toast.success(
        <ToastSpinner message="Registration successful" type="success" />
        );
//        navigate(`/`);
    } else {
        toast.error(
        <ToastSpinner
            message="Registration failed. Please try again."
            type="error"
        />
        , { autoClose: false }
        );
    }
    } catch (error) {
    console.error(error);
    toast.error(
        <ToastSpinner
        message="An error occurred. Please try again later."
        type="error"
        />
        , { autoClose: false }
    );
    }
  };

  const handleCancel = () => {
    toggleModalOtherBrokerage();
  };
 
  return (
    <>
      <StoreProvider>
      <Form
        onSubmit={handleSubmit(onSubmit)}
        className="form"
        autoComplete="off"
      >
        <Container className="">
            <Row className="d-flex justify-content-center mb-3">
            <Col md={4} xs={12} className="form-group">
                <FormInputText
                name="first_name"
                control={control}
                label="First Name"
                />
            </Col>
            <Col md={4} xs={12} className="form-group">
                <FormInputText
                name="last_name"
                control={control}
                label="Last Name"
                />
            </Col>
            </Row>
            <Row className="d-flex justify-content-center mb-3">
            <Col md={4} xs={12} className="form-group">
                <FormInputText name="username" control={control} label="Email" />
            </Col>
            <Col xs={12} md={4} className="form-group">
                <FormInputText name="phone" control={control} label="Mobile" />
            </Col>
            </Row>
            <Row className="d-flex justify-content-center mb-3">
                <Col
                xs={12}
                md={4}
                className="form-group rl-dropdown-content rl-dropdown-multilist rl-select-noinput"
                >
                <FormInputDropdown
                    placeholder="Assign Market"
                    name="market"
                    control={control}
                    label="Select Market"
                    options={marketOptions}
                    setValue={setValue}
                    customOnChange={(e) => handleMarketChange(e)}
                    defaultValue=""
                    showEndAdornment={false}
                />
                </Col>
                <Col
                xs={12}
                md={4}
                className="form-group rl-dropdown-content rl-dropdown-option rl-dropdown-custom select-agent rl-dropdown-select rl-brokerage-input"
                >
                <FormInputAsyncSelect
                    placeholder="Select Brokerage"
                    name="brokerage"
                    customOnChange={(e) =>
                    handleBrokerChange(e)
                    }
                    control={control}
                    filter={{
                    marketId: marketId,
                    }}
                    type="withBrokerageOther"
                    label="Brokerage - Choose other if not listed"
                    disabled={marketId ? false : true}
                    showEndAdornment={false}
                />
                </Col>
            </Row>
            <Row className="d-flex justify-content-center mb-3">
            <Col xs={12} md={4} className="form-group">
                <FormInputText
                name="password_hash"
                control={control}
                label="Password"
                type={password === "password" ? "password" : "text"}
                endAdornment={
                    <span onClick={togglePassword} className="show-hide-icon">
                    {password === "password" ? <EyeSlash /> : <Eye />}
                    </span>
                }
                />
            </Col>
            <Col xs={12} md={4} className="form-group">
                <FormInputText
                name="confirmPassword"
                control={control}
                label="Re-type Password"
                type={passwordConfirm === "password" ? "password" : "text"}
                endAdornment={
                    <span
                    onClick={toggleConfirmPassword}
                    className="show-hide-icon"
                    >
                    {passwordConfirm === "password" ? <EyeSlash /> : <Eye />}
                    </span>
                }
                />
            </Col>
            </Row>

            <div className="flex_center mb-3">
            <div className="form-group no-asterisk rl_radio_form">
                <FormInputCheckbox
                name="acceptTerms"
                control={control}
                options={acceptTerms}
                setValue={setValue}
                label={
                    <>
                    I agree to{" "}
                    <a
                        href="https://rocketlisterwp.azurewebsites.net/terms-and-conditions"
                        target="_blank"
                        rel="noopener noreferrer"
                    >
                        Terms and Services
                    </a>
                    </>
                }
                />
            </div>
            </div>
            <CustomButton
            type="submit"
            className="btn btn-primary mb-3 rl-btn-primary"
            >
            Register
            </CustomButton>
        </Container>
    </Form>
    </StoreProvider>

      {/* Modal when other is chosen for brokerage */}
      <CustomModal
        showConfirmCallToAction={true}
        show={showModalOtherBrokerage}
        close={toggleModalOtherBrokerage}
        footer={false}
        header={true}
        headerTitle="Add New Brokerage"
        size="sm"
        className="rl-custom-modal"
      >
        <Modal.Body className="reset-password ">
          <Form
            onSubmit={brokerageHandleSubmit(onSubmitBrokerage)}
            className="form mb-4"
          >
            <Row className="mb-3">
              <Col className="form-group text-start">
                <FormInputText
                  name="name"
                  control={brokerageControl}
                  label="Brokerage"
                />
              </Col>
            </Row>
            <Row className="mt-4 rl-modal-btn">
              <Col md={12} className="rl-button-group ">
                <CustomButton type="submit" className="rl-btn-primary mx-2">
                  Save
                </CustomButton>
                <CustomButton
                  className="rl-btn-secondary"
                  onClick={() => handleCancel()}
                >
                  Cancel
                </CustomButton>
              </Col>
            </Row>
          </Form>
        </Modal.Body>
      </CustomModal>
    </>
  );
};
export default CompactRegistrationForm;

To be clear, the first screenshot is the actual page that I created with the visual editor by dropping the registration component into it. So the custom component is actually working. It simply looks wrong in the visual editor. How do I fix this?

Hello @AQuirky

Could you please share the error message from either the browser console or your local server terminal?

Additionally, could you confirm which Builder SDK you are using and the version number?

Thank you,

Hi manish-sharma,

This is all working now. I gave many of the important details in my response to my own question on using bootstrap in the visual editor preview. Briefly, I had to include the bootstrap css file in the top level layout.tsx file. So here is the way my registration form looks now in the visual editor…

So I am finally making some headway after banging my head against the wall for weeks and weeks. The fundamental problem is that I am off the main road. If my legacy application had been NextJS and Material I would be done already because that is what all the examples are coded in. Instead I started with a CRA app and Bootstrap.

So I am not done yet. I finally have some confidence now and am working on making the visual editor experience really good. I am creating Row and Col Bootstrap custom components with user selectable bootstrap classes to control the width of the columns (col-2, col-4, col-8, col-12, etc.). So the user will be able to drag and drop a bootstrap grid into the editor page and then add child components in each grid cell. It will be so much simpler and more flexible then the double and triple column custom component examples I have seen in the examples.