Typescript implementation using React Hook Form 2023.
tldr; Complete code below for those in a hurry.
NOTE: The code below is a simplified version of a much longer piece which uses zod. To keep things simple I deleted some parts.
export const EmailForm: React.FC = () => {
const {register, handleSubmit, formState: {errors}, setValue} = useForm();
// Optional: for when the <label> gets clicked
const emailRef = useRef<HTMLDivElement>(null);
const onSubmit = async (data: FieldValues) => {
// Send data here. In catch statement use setError or formError
console.log(data);
}
const onInput = (e: FormEvent<HTMLInputElement>) => {
setValue('email', e.currentTarget.innerText);
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* The actual field which react hook form validates */}
<input type="hidden" {...register('email')} />
<div>
<label onClick={() => emailRef.current?.focus()}>Email</label>
<div contentEditable onInput={onInput} ref={emailRef}
role="textbox" tabIndex={0}></div>
<div>{errors.email?.message}</div>
</div>
<div>
<button type="submit">Submit</button>
</div>
</form>
);
}
Essentially, what you want is to make the div act like a <textarea>
while keeping the functionality. The benefits of this varies but I usually do it so it expands automatically while the user types.
Register with React Hook Form
Since this is React, I'm using RHF to manage the content so there's a hidden field we need to populate while the user types. This can also be a <textarea>
but just make sure it's invisible.
<input type="hidden" {...register('email')} />
Handling data
A <div>
has no knowledge of what an onChange
event is but it responds well to the onInput
event so use that instead. Also add role
and tabIndex
to assist in usablity as seen in this so.
<label onClick={() => emailRef.current?.focus()}>Email</label>
<div contentEditable onInput={onInput} ref={emailRef}
role="textbox" tabIndex={0}></div>
Optionally, if you want to make the <label>
focus on the <div>
when clicked like a label should then use useRef
to get it working:
const emailRef = useRef<HTMLDivElement>(null);
With the onInput={onInput}
callback in place, now everytime the user types something the setValue
function gets called allowing RHF access to the data as if it were any other field. This in turn assists in generating error messages:
<div>{errors.email?.message}</div>
Lastly, when the form is submitted the onSubmit
callback is triggered. From there you can send it to your server.