Files
tailwind-ui-library/src/form/Form.tsx
T
tben d07cbb7c84 Don't crash Form's change handler outside a form
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.
2026-10-22 05:00:30 +01:00

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>
);
}