I am looking at an Accessibility Form done in Angular with Angular Bootstrap, and I am trying to recreate it in React. Unfortunately, I can't use React Bootstrap but must use MUI Material instead. I have created the form using react-hook-form and put in validation with Yup. The validation is all working. However, the error messages appear under the input fields. I need the error messages to appear on the right side of the input fields, in a box with a border, like you see on the first image below.
I tried to use plain CSS to move the message, but that didn't work. The Angular form used a Bootstrap Popover for the messages, so I am trying to use the MUI Material Popover. I have only put the Popover on the first input field, for First Name. I have looked at the MUI Docs and various Stack Overflow questions and have attempted to correctly anchor the Popover to the input field.
The anchor is not working though. In fact, it appears to be anchored to the form (the Dialog component) itself, not to the input field. The second and third images below shows what happens when I set anchorOrigin.horizontal and transformOrigin.horizontal to right and left, and left and right. If I do center for the horizontals, it is in the middle of the form. Although vertical is set to center, the Popover appears a little higher than center. But if I put top or bottom for the vertical, the Popover ends up at the top or bottom of the form.
the anchorEl property for the Popover is currently set to anchorEl, which is set in useEffect to the ref for the input field, firstNameRef. I also tried putting firstNameRef and firstNameRef.current as the value for anchorEl directly in the Popover component, but that didn't work. I also tried using anchorReference and anchorPosition, but that is extremely hard to line everything up correctly, and I have a feeling it would be very brittle.
I have no idea what I'm doing wrong.
Here is my code:
import React, { useEffect, useState, useRef } from "react";
import { useForm } from "react-hook-form";
import { Box, Dialog, Popover } from "@mui/material";
import * as Yup from "yup";
import { yupResolver } from "@hookform/resolvers/yup";
import "./accessibility.scss";
export default function Accessibility(props: any) {
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
const validationSchema = Yup.object().shape({
firstName: Yup.string()
.max(20)
.required("Looks like you didn't enter your First Name."),
lastName: Yup.string()
.max(50)
.required("Looks like you didn't enter your Last Name."),
phone: Yup.string()
.max(12)
.matches(
/^(\+\d{1,2}\s?)?1?\-?\.?\s?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$/,
"Invalid phone number format"
),
emailAddress: Yup.string()
.max(55)
.required("Looks like you didn't enter your Email Address."),
comments: Yup.string()
.max(100)
.required("Looks like you didn't enter your Comments."),
});
const firstNameRef = useRef();
useEffect(() => {
setAnchorEl(firstNameRef.current);
}, [firstNameRef]);
const {
register,
handleSubmit,
watch,
formState: { errors },
} = useForm({
resolver: yupResolver(validationSchema)
});
console.log(watch());
const handleClose = props.handleClose;
console.log(errors);
return (
<Dialog
open={props.open}
onClose={props.handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box>
<div className="d-block modal overflow-auto">
<div className="modal-dialog modal-dialog-centered modal-lg">
<div className="modal-content">
<div className="spectrum-banner">
<div className="modal-header pb-0">
<button
type="button"
className="close"
aria-label="Close"
onClick={handleClose}
data-automation-id="modal-close"
>
<span aria-hidden="true">×</span>
</button>
</div>
<div className="modal-body ng-star-inserted">
<div className="row">
<div className="col-12" id="modal_title">
<div dangerouslySetInnerHTML={{ __html: props.title }}></div>
</div>
<div className="col-12">
<form
className="w-100"
onSubmit={handleSubmit((data) => {
console.log(data);
})}
>
<div className="row">
<div className="col-md-6 col-lg-4">
<label htmlFor="">First Name (Required)</label>
<input
ref={firstNameRef}
style={{
backgroundColor: errors["firstName"] ? "#ffc7c7" : null,
border: errors["firstName"] ? "2px solid #ec3434" : "1px solid #ced4da",
}}
{...register("firstName")}
type="text"
placeholder="First Name"
className="form-control"
/>
<Popover
open={!!errors.firstName}
anchorEl={firstNameRef.current}
anchorOrigin={{
vertical: "center",
horizontal: "right",
}}
transformOrigin={{
vertical: "center",
horizontal: "left"
}}
>
{errors.firstName && <div className="popover-body">{errors.firstName.message}</div>}
</Popover>
{/* {errors.firstName && <div className="popover-body">{errors.firstName.message}</div>} */}
</div>
</div>
<div className="row">
<div className="col-md-6 col-lg-4">
<label htmlFor="">Last Name (Required)</label>
<input
style={{
backgroundColor: errors["lastName"] ? "#ffc7c7" : null,
border: errors["lastName"] ? "2px solid #ec3434" : "1px solid #ced4da"
}}
{...register("lastName")}
type="text"
placeholder="Last Name"
className="form-control"
/>
{errors.lastName && <span>{errors.lastName.message}</span>}
</div>
</div>
<div className="row">
<div className="col-md-8 col-lg-6">
<label htmlFor="">Phone Number (Optional)</label>
<input
{...register("phone")}
type="text"
// inputmode="numeric"
// mask="000-000-0000"
placeholder="___-___-____"
className="form-control"
// maxlength="12" autocomplete="tel-national"
/>
{errors.phone && <p>{errors.phone.message}</p>}
</div>
</div>
<div className="row">
<div className="col-md-8 col-lg-6">
<label htmlFor="">Email Address (Required)</label>
<input
style={{
backgroundColor: errors["emailAddress"] ? "#ffc7c7" : null,
border: errors["emailAddress"] ? "2px solid #ec3434" : "1px solid #ced4da"
}}
{...register("emailAddress")}
placeholder="[email protected]"
className="form-control"
/>
{errors.emailAddress && <p>{errors.emailAddress.message}</p>}
</div>
</div>
<div className="row">
<div className="col-md-8 col-lg-6">
<label htmlFor="">Comments (Required)</label>
<textarea
style={{
backgroundColor: errors["comments"] ? "#ffc7c7" : null,
border: errors["comments"] ? "2px solid #ec3434" : "1px solid #ced4da"
}}
{...register("comments")}
placeholder="Comments"
className="form-control"
></textarea>
{errors.comments && <p>{errors.comments.message}</p>}
</div>
</div>
<div className="my-2">
<input className="pm-button" type="submit" />
</div>
{/* <form-message
[message]="accessibilityContactFormMessage"
>
</form-message> */}
</form>
</div>
</div>
<div
className="row"
// *ngIf="emailSent"
>
<div className="col-12 text-center py-5">
<div className="col-12"></div>
</div>
</div>
</div>
<div className="modal-footer">
<div
className="w-100 m-0 font-rem-0_75"
// *ngIf="!emailSent"
>
<p className="mb-3">
Need other assistance? Our
<a className="color-blue">
Customer Service Center
</a>{" "}
provides more ways to connect.
</p>
</div>
<p
className="w-100 m-0 font-rem-0_75"
data-automation-id="accessibility-contact-modal-form-number"
// *ngIf="!emailSent"
>
{props.formNumber}
</p>
<p
className="w-100 m-0 font-rem-0_75"
data-automation-id="accessibility-contact-confirmation-modal-form-number"
// *ngIf="emailSent"
></p>
</div>
</div>
</div>
</div>
</div>
</Box>
</Dialog>
);
}
Here are the screen images. The first is the Angular form I'm trying to replicate. The two others are showing the problem with the Popover.


