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 }) => {
const { register, formState, control } = useFormContext();
const { formState, control } = useFormContext();
const { formatNumber } = useLocalization();
const pricesWatch = useWatch({ control, name: ["subtotal_price", "discount", "tax"] });
@ -105,9 +105,7 @@ export const QuotePricesResume = ({ currency }: { currency: CurrencyData }) => {
scale={2}
disabled={formState.disabled}
placeholder={t("quotes.form_fields.discount.placeholder")}
{...register("discount", {
required: false,
})}
name='discount'
onChange={(value) => {
console.log("discount", value);
}}
@ -134,9 +132,7 @@ export const QuotePricesResume = ({ currency }: { currency: CurrencyData }) => {
scale={2}
disabled={formState.disabled}
placeholder={t("quotes.form_fields.tax.placeholder")}
{...register("tax", {
required: false,
})}
name='tax'
/>
</div>
<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;
}) => {
const { toast } = useToast();
const { control, register } = useFormContext();
const { control } = useFormContext();
const [pickerMode] = useState<"dialog" | "panel">("dialog");
@ -65,7 +65,7 @@ export const QuoteDetailsCardEditor = ({
<FormTextAreaField
readOnly={original?.id_article}
autoSize
{...register(`items.${index}.id_article`)}
name={`items.${index}.id_article`}
/>
);
},
@ -80,7 +80,7 @@ export const QuoteDetailsCardEditor = ({
<FormTextAreaField
readOnly={original?.id_article}
autoSize
{...register(`items.${index}.description`)}
name={`items.${index}.description`}
/>
);
},
@ -98,7 +98,7 @@ export const QuoteDetailsCardEditor = ({
<FormQuantityField
scale={0}
className='text-right'
{...register(`items.${index}.quantity`)}
name={`items.${index}.quantity`}
/>
);
},
@ -117,7 +117,7 @@ export const QuoteDetailsCardEditor = ({
language={language}
scale={2}
className='text-right'
{...register(`items.${index}.unit_price`)}
name={`items.${index}.unit_price`}
/>
);
},
@ -138,7 +138,7 @@ export const QuoteDetailsCardEditor = ({
scale={2}
readOnly
className='text-right'
{...register(`items.${index}.subtotal_price`)}
name={`items.${index}.subtotal_price`}
/>
);
},
@ -155,7 +155,7 @@ export const QuoteDetailsCardEditor = ({
<FormPercentageField
scale={2}
className='text-right'
{...register(`items.${index}.discount`)}
name={`items.${index}.discount`}
/>
);
},
@ -176,7 +176,7 @@ export const QuoteDetailsCardEditor = ({
scale={2}
readOnly
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";
export const QuoteGeneralCardEditor = () => {
const { register, formState } = useFormContext();
const { formState } = useFormContext();
return (
<div className='grid gap-6 md:grid-cols-6'>
@ -21,7 +21,7 @@ export const QuoteGeneralCardEditor = () => {
description={t("quotes.form_fields.customer_reference.desc")}
disabled={formState.disabled}
placeholder={t("quotes.form_fields.customer_reference.placeholder")}
{...register("customer_reference")}
name='customer_reference'
/>
<FormDatePickerField
@ -30,18 +30,14 @@ export const QuoteGeneralCardEditor = () => {
description={t("quotes.form_fields.date.desc")}
disabled={formState.disabled}
placeholder={t("quotes.form_fields.date.placeholder")}
{...register("date", {
required: true,
})}
name='date'
/>
<FormTextField
label={t("quotes.form_fields.validity.label")}
description={t("quotes.form_fields.validity.desc")}
disabled={formState.disabled}
placeholder={t("quotes.form_fields.validity.placeholder")}
{...register("validity", {
required: false,
})}
name='validity'
/>
<FormTextAreaField
className='col-span-2'
@ -49,9 +45,7 @@ export const QuoteGeneralCardEditor = () => {
description={t("quotes.form_fields.payment_method.desc")}
disabled={formState.disabled}
placeholder={t("quotes.form_fields.payment_method.placeholder")}
{...register("payment_method", {
required: false,
})}
name='payment_method'
/>
</div>
<div className='grid grid-cols-6 gap-6'>
@ -62,9 +56,7 @@ export const QuoteGeneralCardEditor = () => {
description={t("quotes.form_fields.customer_information.desc")}
disabled={formState.disabled}
placeholder={t("quotes.form_fields.customer_information.placeholder")}
{...register("customer_information", {
required: true,
})}
name='customer_information'
errors={formState.errors}
/>
@ -74,9 +66,7 @@ export const QuoteGeneralCardEditor = () => {
description={t("quotes.form_fields.notes.desc")}
disabled={formState.disabled}
placeholder={t("quotes.form_fields.notes.placeholder")}
{...register("notes", {
required: false,
})}
name='notes'
/>
</div>
</FormGroup>
@ -105,9 +95,7 @@ export const QuoteGeneralCardEditor = () => {
description={t("quotes.form_fields.lang_code.desc")}
//disabled={formState.disabled}
placeholder={t("quotes.form_fields.lang_code.placeholder")}
{...register("lang_code", {
required: true,
})}
name='lang_code'
/>
<FormTextField
required
@ -115,9 +103,7 @@ export const QuoteGeneralCardEditor = () => {
description={t("quotes.form_fields.currency_code.desc")}
//disabled={formState.disabled}
placeholder={t("quotes.form_fields.currency_code.placeholder")}
{...register("currency_code", {
required: true,
})}
name='currency_code'
/>
</div>
</FormGroup>

View File

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

View File

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

View File

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

View File

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

View File

@ -33,8 +33,7 @@ export type FormTextFieldProps<
UseControllerProps<TFieldValues, TName> &
VariantProps<typeof formTextFieldVariants>;
export const FormTextField = React.forwardRef<HTMLInputElement, FormTextFieldProps>(
(props /*ref*/) => {
export const FormTextField = (props: FormTextFieldProps) => {
const {
name,
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 { createRoot } from "react-dom/client";
import { FormProvider, UseFormReturn, useForm } from "react-hook-form";
import { FormTextField } from "@/components/Forms/FormTextField";
import { FormField } from "./form";
type TestFormValues = {
items: Array<{ quantity: string }>;
title: string;
};
declare global {
@ -41,6 +43,7 @@ const FormHarness = ({ name, onMount, onFormReady }: FormHarnessProps) => {
const form = useForm<TestFormValues>({
defaultValues: {
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", () => {
let container: HTMLDivElement;
let root: ReturnType<typeof createRoot>;
@ -98,4 +120,23 @@ describe("FormField", () => {
expect((container.querySelector("input[aria-label='quantity']") as HTMLInputElement).value).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");
});
});