Form reads the closest form on every change. A field rendered through a portal (a Modal) has no form ancestor in the DOM, so FormData(null) threw. Change handlers now get an empty object instead.
55 lines
1.9 KiB
TypeScript
55 lines
1.9 KiB
TypeScript
import _ from "lodash";
|
|
import type { DetailedHTMLProps, FormHTMLAttributes, RefObject } from "react";
|
|
import { twMerge } from "tailwind-merge";
|
|
|
|
type Props<T extends { [key: string]: any } = { [key: string]: any }> =
|
|
DetailedHTMLProps<FormHTMLAttributes<HTMLFormElement>, HTMLFormElement> & {
|
|
submitHandler?: (e: React.FormEvent<HTMLFormElement>, data: T) => void;
|
|
changeHandler?: (e: React.FormEvent<HTMLFormElement>, data: T) => void;
|
|
formRef?: RefObject<HTMLFormElement>;
|
|
};
|
|
|
|
/**
|
|
* # Form Element
|
|
* @className twui-form
|
|
*/
|
|
export default function Form<
|
|
T extends { [key: string]: any } = { [key: string]: any },
|
|
>({ formRef, ...props }: Props<T>) {
|
|
const finalProps = _.omit(props, ["submitHandler", "changeHandler"]);
|
|
|
|
return (
|
|
<form
|
|
{...finalProps}
|
|
className={twMerge(
|
|
"flex flex-col items-stretch gap-2 w-full bg-transparent",
|
|
"twui-form",
|
|
props.className,
|
|
)}
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
const formEl = e.target as HTMLFormElement;
|
|
const formData = new FormData(formEl);
|
|
const data = Object.fromEntries(formData.entries()) as T;
|
|
props.submitHandler?.(e, data);
|
|
props.onSubmit?.(e);
|
|
}}
|
|
onChange={(e) => {
|
|
e.preventDefault();
|
|
const taregtEl = e.target as HTMLElement;
|
|
const formEl = taregtEl.closest("form") as HTMLFormElement | null;
|
|
const data = formEl
|
|
? (Object.fromEntries(
|
|
new FormData(formEl).entries(),
|
|
) as T)
|
|
: ({} as T);
|
|
props.changeHandler?.(e, data);
|
|
props.onChange?.(e);
|
|
}}
|
|
ref={formRef}
|
|
>
|
|
{props.children}
|
|
</form>
|
|
);
|
|
}
|