Refactor formularios para eliminar el uso de register en favor de name en los campos de formulario, mejorando la legibilidad y simplificando la gestión de formularios.

This commit is contained in:
David Arranz 2026-09-16 19:43:10 +02:00
parent 3eb0396cbd
commit 6a79ea2299
9 changed files with 75 additions and 68 deletions

View File

@ -66,7 +66,7 @@ const calculateQuoteTotals = (prices: any) => {
}; };
export const QuotePricesResume = ({ currency }: { currency: CurrencyData }) => { export const QuotePricesResume = ({ currency }: { currency: CurrencyData }) => {
const { register, formState, control } = useFormContext(); const { formState, control } = useFormContext();
const { formatNumber } = useLocalization(); const { formatNumber } = useLocalization();
const pricesWatch = useWatch({ control, name: ["subtotal_price", "discount", "tax"] }); const pricesWatch = useWatch({ control, name: ["subtotal_price", "discount", "tax"] });
@ -105,9 +105,7 @@ export const QuotePricesResume = ({ currency }: { currency: CurrencyData }) => {
scale={2} scale={2}
disabled={formState.disabled} disabled={formState.disabled}
placeholder={t("quotes.form_fields.discount.placeholder")} placeholder={t("quotes.form_fields.discount.placeholder")}
{...register("discount", { name='discount'
required: false,
})}
onChange={(value) => { onChange={(value) => {
console.log("discount", value); console.log("discount", value);
}} }}
@ -134,9 +132,7 @@ export const QuotePricesResume = ({ currency }: { currency: CurrencyData }) => {
scale={2} scale={2}
disabled={formState.disabled} disabled={formState.disabled}
placeholder={t("quotes.form_fields.tax.placeholder")} placeholder={t("quotes.form_fields.tax.placeholder")}
{...register("tax", { name='tax'
required: false,
})}
/> />
</div> </div>
<div className='grid gap-1 font-semibold text-muted-foreground'> <div className='grid gap-1 font-semibold text-muted-foreground'>

View File

@ -31,7 +31,7 @@ export const QuoteDetailsCardEditor = ({
defaultValues: Readonly<{ [x: string]: any }> | undefined; defaultValues: Readonly<{ [x: string]: any }> | undefined;
}) => { }) => {
const { toast } = useToast(); const { toast } = useToast();
const { control, register } = useFormContext(); const { control } = useFormContext();
const [pickerMode] = useState<"dialog" | "panel">("dialog"); const [pickerMode] = useState<"dialog" | "panel">("dialog");
@ -65,7 +65,7 @@ export const QuoteDetailsCardEditor = ({
<FormTextAreaField <FormTextAreaField
readOnly={original?.id_article} readOnly={original?.id_article}
autoSize autoSize
{...register(`items.${index}.id_article`)} name={`items.${index}.id_article`}
/> />
); );
}, },
@ -80,7 +80,7 @@ export const QuoteDetailsCardEditor = ({
<FormTextAreaField <FormTextAreaField
readOnly={original?.id_article} readOnly={original?.id_article}
autoSize autoSize
{...register(`items.${index}.description`)} name={`items.${index}.description`}
/> />
); );
}, },
@ -98,7 +98,7 @@ export const QuoteDetailsCardEditor = ({
<FormQuantityField <FormQuantityField
scale={0} scale={0}
className='text-right' className='text-right'
{...register(`items.${index}.quantity`)} name={`items.${index}.quantity`}
/> />
); );
}, },
@ -117,7 +117,7 @@ export const QuoteDetailsCardEditor = ({
language={language} language={language}
scale={2} scale={2}
className='text-right' className='text-right'
{...register(`items.${index}.unit_price`)} name={`items.${index}.unit_price`}
/> />
); );
}, },
@ -138,7 +138,7 @@ export const QuoteDetailsCardEditor = ({
scale={2} scale={2}
readOnly readOnly
className='text-right' className='text-right'
{...register(`items.${index}.subtotal_price`)} name={`items.${index}.subtotal_price`}
/> />
); );
}, },
@ -155,7 +155,7 @@ export const QuoteDetailsCardEditor = ({
<FormPercentageField <FormPercentageField
scale={2} scale={2}
className='text-right' className='text-right'
{...register(`items.${index}.discount`)} name={`items.${index}.discount`}
/> />
); );
}, },
@ -176,7 +176,7 @@ export const QuoteDetailsCardEditor = ({
scale={2} scale={2}
readOnly readOnly
className='font-semibold text-right' className='font-semibold text-right'
{...register(`items.${index}.total_price`)} name={`items.${index}.total_price`}
/> />
); );
}, },

View File

@ -4,7 +4,7 @@ import { t } from "i18next";
import { useFormContext } from "react-hook-form"; import { useFormContext } from "react-hook-form";
export const QuoteGeneralCardEditor = () => { export const QuoteGeneralCardEditor = () => {
const { register, formState } = useFormContext(); const { formState } = useFormContext();
return ( return (
<div className='grid gap-6 md:grid-cols-6'> <div className='grid gap-6 md:grid-cols-6'>
@ -21,7 +21,7 @@ export const QuoteGeneralCardEditor = () => {
description={t("quotes.form_fields.customer_reference.desc")} description={t("quotes.form_fields.customer_reference.desc")}
disabled={formState.disabled} disabled={formState.disabled}
placeholder={t("quotes.form_fields.customer_reference.placeholder")} placeholder={t("quotes.form_fields.customer_reference.placeholder")}
{...register("customer_reference")} name='customer_reference'
/> />
<FormDatePickerField <FormDatePickerField
@ -30,18 +30,14 @@ export const QuoteGeneralCardEditor = () => {
description={t("quotes.form_fields.date.desc")} description={t("quotes.form_fields.date.desc")}
disabled={formState.disabled} disabled={formState.disabled}
placeholder={t("quotes.form_fields.date.placeholder")} placeholder={t("quotes.form_fields.date.placeholder")}
{...register("date", { name='date'
required: true,
})}
/> />
<FormTextField <FormTextField
label={t("quotes.form_fields.validity.label")} label={t("quotes.form_fields.validity.label")}
description={t("quotes.form_fields.validity.desc")} description={t("quotes.form_fields.validity.desc")}
disabled={formState.disabled} disabled={formState.disabled}
placeholder={t("quotes.form_fields.validity.placeholder")} placeholder={t("quotes.form_fields.validity.placeholder")}
{...register("validity", { name='validity'
required: false,
})}
/> />
<FormTextAreaField <FormTextAreaField
className='col-span-2' className='col-span-2'
@ -49,9 +45,7 @@ export const QuoteGeneralCardEditor = () => {
description={t("quotes.form_fields.payment_method.desc")} description={t("quotes.form_fields.payment_method.desc")}
disabled={formState.disabled} disabled={formState.disabled}
placeholder={t("quotes.form_fields.payment_method.placeholder")} placeholder={t("quotes.form_fields.payment_method.placeholder")}
{...register("payment_method", { name='payment_method'
required: false,
})}
/> />
</div> </div>
<div className='grid grid-cols-6 gap-6'> <div className='grid grid-cols-6 gap-6'>
@ -62,9 +56,7 @@ export const QuoteGeneralCardEditor = () => {
description={t("quotes.form_fields.customer_information.desc")} description={t("quotes.form_fields.customer_information.desc")}
disabled={formState.disabled} disabled={formState.disabled}
placeholder={t("quotes.form_fields.customer_information.placeholder")} placeholder={t("quotes.form_fields.customer_information.placeholder")}
{...register("customer_information", { name='customer_information'
required: true,
})}
errors={formState.errors} errors={formState.errors}
/> />
@ -74,9 +66,7 @@ export const QuoteGeneralCardEditor = () => {
description={t("quotes.form_fields.notes.desc")} description={t("quotes.form_fields.notes.desc")}
disabled={formState.disabled} disabled={formState.disabled}
placeholder={t("quotes.form_fields.notes.placeholder")} placeholder={t("quotes.form_fields.notes.placeholder")}
{...register("notes", { name='notes'
required: false,
})}
/> />
</div> </div>
</FormGroup> </FormGroup>
@ -105,9 +95,7 @@ export const QuoteGeneralCardEditor = () => {
description={t("quotes.form_fields.lang_code.desc")} description={t("quotes.form_fields.lang_code.desc")}
//disabled={formState.disabled} //disabled={formState.disabled}
placeholder={t("quotes.form_fields.lang_code.placeholder")} placeholder={t("quotes.form_fields.lang_code.placeholder")}
{...register("lang_code", { name='lang_code'
required: true,
})}
/> />
<FormTextField <FormTextField
required required
@ -115,9 +103,7 @@ export const QuoteGeneralCardEditor = () => {
description={t("quotes.form_fields.currency_code.desc")} description={t("quotes.form_fields.currency_code.desc")}
//disabled={formState.disabled} //disabled={formState.disabled}
placeholder={t("quotes.form_fields.currency_code.placeholder")} placeholder={t("quotes.form_fields.currency_code.placeholder")}
{...register("currency_code", { name='currency_code'
required: true,
})}
/> />
</div> </div>
</FormGroup> </FormGroup>

View File

@ -43,8 +43,7 @@ export type FormCurrencyFieldProps<
scale: number; scale: number;
}; };
export const FormCurrencyField = React.forwardRef<HTMLInputElement, FormCurrencyFieldProps>( export const FormCurrencyField = (props: FormCurrencyFieldProps) => {
(props /*ref*/) => {
const { const {
name, name,
label, label,
@ -156,7 +155,6 @@ export const FormCurrencyField = React.forwardRef<HTMLInputElement, FormCurrency
}} }}
/> />
); );
} };
);
FormCurrencyField.displayName = "FormCurrencyField"; FormCurrencyField.displayName = "FormCurrencyField";

View File

@ -42,10 +42,7 @@ export type FormPercentageFieldProps<
scale: number; scale: number;
}; };
export const FormPercentageField = React.forwardRef< export const FormPercentageField = (props: FormPercentageFieldProps) => {
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & FormPercentageFieldProps
>((props /*ref*/) => {
const { const {
name, name,
label, label,
@ -144,4 +141,4 @@ export const FormPercentageField = React.forwardRef<
}} }}
/> />
); );
}); };

View File

@ -41,8 +41,7 @@ export type FormQuantityFieldProps<
scale: number; scale: number;
}; };
export const FormQuantityField = React.forwardRef<HTMLInputElement, FormQuantityFieldProps>( export const FormQuantityField = (props: FormQuantityFieldProps) => {
(props /*ref*/) => {
const { const {
name, name,
label, label,
@ -140,5 +139,4 @@ export const FormQuantityField = React.forwardRef<HTMLInputElement, FormQuantity
}} }}
/> />
); );
} };
);

View File

@ -46,11 +46,7 @@ export type FormTextAreaFieldProps<
errors?: FieldErrors<TFieldValues>; errors?: FieldErrors<TFieldValues>;
} & VariantProps<typeof formTextAreaFieldVariants>; } & VariantProps<typeof formTextAreaFieldVariants>;
export const FormTextAreaField = React.forwardRef< export const FormTextAreaField = ({
HTMLDivElement,
React.TextareaHTMLAttributes<HTMLTextAreaElement> & FormTextAreaFieldProps
>(
({
name, name,
label, label,
hint, hint,
@ -64,11 +60,9 @@ export const FormTextAreaField = React.forwardRef<
variant, variant,
...props ...props
}) => }: React.TextareaHTMLAttributes<HTMLTextAreaElement> & FormTextAreaFieldProps) => {
/*ref*/ const { control } = useFormContext();
{ return (
const { control } = useFormContext();
return (
<FormField <FormField
control={control} control={control}
name={name} name={name}
@ -108,6 +102,5 @@ export const FormTextAreaField = React.forwardRef<
</FormItem> </FormItem>
)} )}
/> />
); );
} };
);

View File

@ -33,8 +33,7 @@ export type FormTextFieldProps<
UseControllerProps<TFieldValues, TName> & UseControllerProps<TFieldValues, TName> &
VariantProps<typeof formTextFieldVariants>; VariantProps<typeof formTextFieldVariants>;
export const FormTextField = React.forwardRef<HTMLInputElement, FormTextFieldProps>( export const FormTextField = (props: FormTextFieldProps) => {
(props /*ref*/) => {
const { const {
name, name,
label, label,
@ -87,5 +86,4 @@ export const FormTextField = React.forwardRef<HTMLInputElement, FormTextFieldPro
}} }}
/> />
); );
} };
);

View File

@ -1,10 +1,12 @@
import { act, useEffect, useRef } from "react"; import { act, useEffect, useRef } from "react";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import { FormProvider, UseFormReturn, useForm } from "react-hook-form"; import { FormProvider, UseFormReturn, useForm } from "react-hook-form";
import { FormTextField } from "@/components/Forms/FormTextField";
import { FormField } from "./form"; import { FormField } from "./form";
type TestFormValues = { type TestFormValues = {
items: Array<{ quantity: string }>; items: Array<{ quantity: string }>;
title: string;
}; };
declare global { declare global {
@ -41,6 +43,7 @@ const FormHarness = ({ name, onMount, onFormReady }: FormHarnessProps) => {
const form = useForm<TestFormValues>({ const form = useForm<TestFormValues>({
defaultValues: { defaultValues: {
items: [{ quantity: "1" }, { quantity: "2" }], items: [{ quantity: "1" }, { quantity: "2" }],
title: "before",
}, },
}); });
@ -55,6 +58,25 @@ const FormHarness = ({ name, onMount, onFormReady }: FormHarnessProps) => {
); );
}; };
const ControlledTextFieldHarness = ({ onFormReady }: Pick<FormHarnessProps, "onFormReady">) => {
const form = useForm<TestFormValues>({
defaultValues: {
items: [],
title: "before",
},
});
useEffect(() => {
onFormReady(form);
}, [form, onFormReady]);
return (
<FormProvider {...form}>
<FormTextField name='title' />
</FormProvider>
);
};
describe("FormField", () => { describe("FormField", () => {
let container: HTMLDivElement; let container: HTMLDivElement;
let root: ReturnType<typeof createRoot>; let root: ReturnType<typeof createRoot>;
@ -98,4 +120,23 @@ describe("FormField", () => {
expect((container.querySelector("input[aria-label='quantity']") as HTMLInputElement).value).toBe("5"); expect((container.querySelector("input[aria-label='quantity']") as HTMLInputElement).value).toBe("5");
expect(form?.getValues("items.1.quantity")).toBe("5"); expect(form?.getValues("items.1.quantity")).toBe("5");
}); });
it("updates React Hook Form through a Controller without an external register spread", () => {
let form: UseFormReturn<TestFormValues> | undefined;
act(() => {
root.render(<ControlledTextFieldHarness onFormReady={(value) => (form = value)} />);
});
const input = container.querySelector("input[name='title']") as HTMLInputElement;
const valueSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set;
act(() => {
valueSetter?.call(input, "after");
input.dispatchEvent(new Event("input", { bubbles: true }));
});
expect(input.value).toBe("after");
expect(form?.getValues("title")).toBe("after");
});
}); });