commit 2f405d63bd6523e0ccdbbd4e97daa635fe4ddb85
parent 3a92650352eae0386c9b832612a9876721ee67cf
Author: Sebastian <sebasjm@gmail.com>
Date: Tue, 18 Mar 2025 13:18:37 -0300
calculate state and erros on a single iteration
Diffstat:
3 files changed, 101 insertions(+), 147 deletions(-)
diff --git a/packages/web-util/src/forms/forms-types.ts b/packages/web-util/src/forms/forms-types.ts
@@ -194,7 +194,26 @@ type UIFormFieldToggle = {
threeState?: boolean;
} & UIFormFieldBaseConfig;
-export type UIFieldElementDescription = {
+export type ComputableFieldConfig = {
+ /* if the field should be initially hidden */
+ hidden?: boolean;
+
+ /* show a mark as required */
+ required?: boolean;
+
+ /* readonly and dim */
+ disabled?: boolean;
+
+ /*
+ update field config based on form state
+ */
+ updateProps?: (
+ value: any,
+ root?: any,
+ ) => Partial<ComputableFieldConfig> | undefined;
+};
+
+export type UIFieldElementDescription = ComputableFieldConfig & {
/* label if the field, visible for the user */
label: string;
@@ -204,9 +223,6 @@ export type UIFieldElementDescription = {
/* short text to be shown close to the field, usually below and dimmer*/
help?: string;
- /* if the field should be initially hidden */
- hidden?: boolean;
-
/* ui element to show before */
addonBeforeId?: string;
@@ -218,17 +234,12 @@ export type UIFormFieldBaseConfig = UIFieldElementDescription & {
/* example to be shown inside the field */
placeholder?: string;
- /* show a mark as required */
- required?: boolean;
-
- /* readonly and dim */
- disabled?: boolean;
-
/* conversion id to convert the string into the value type
the id should be known to the ui impl
*/
converterId?: string;
+ // FIXME: deprectate this and move to `computableFieldConfig`
/*
return if the field should be hidden.
receives the value after conversion and the root of the form.
diff --git a/packages/web-util/src/forms/gana/VQF_902_1_customer.ts b/packages/web-util/src/forms/gana/VQF_902_1_customer.ts
@@ -43,7 +43,7 @@ member act as director of a domiciliary company, this domiciliary company is the
title: i18n.str`Information on customer`,
description: i18n.str`The customer is a natural person`,
hide(root) {
- return !!root && root["CUSTOMER_INFO_TYPE"] !== "NATURAL_PERSON";
+ return !root || root["CUSTOMER_INFO_TYPE"] !== "NATURAL_PERSON";
},
fields: [
{
@@ -52,7 +52,7 @@ member act as director of a domiciliary company, this domiciliary company is the
type: "text",
required: true,
hide(value, root) {
- return !!root && root["CUSTOMER_INFO_TYPE"] !== "NATURAL_PERSON";
+ return !root || root["CUSTOMER_INFO_TYPE"] !== "NATURAL_PERSON";
},
},
{
@@ -61,7 +61,7 @@ member act as director of a domiciliary company, this domiciliary company is the
type: "textArea",
required: true,
hide(value, root) {
- return !!root && root["CUSTOMER_INFO_TYPE"] !== "NATURAL_PERSON";
+ return !root || root["CUSTOMER_INFO_TYPE"] !== "NATURAL_PERSON";
},
},
{
@@ -70,7 +70,7 @@ member act as director of a domiciliary company, this domiciliary company is the
type: "text",
required: false,
hide(value, root) {
- return !!root && root["CUSTOMER_INFO_TYPE"] !== "NATURAL_PERSON";
+ return !root || root["CUSTOMER_INFO_TYPE"] !== "NATURAL_PERSON";
},
},
{
@@ -79,7 +79,7 @@ member act as director of a domiciliary company, this domiciliary company is the
type: "text",
required: false,
hide(value, root) {
- return !!root && root["CUSTOMER_INFO_TYPE"] !== "NATURAL_PERSON";
+ return !root || root["CUSTOMER_INFO_TYPE"] !== "NATURAL_PERSON";
},
},
{
@@ -90,7 +90,7 @@ member act as director of a domiciliary company, this domiciliary company is the
pattern: "dd/MM/yyyy",
required: true,
hide(value, root) {
- return !!root && root["CUSTOMER_INFO_TYPE"] !== "NATURAL_PERSON";
+ return !root || root["CUSTOMER_INFO_TYPE"] !== "NATURAL_PERSON";
},
},
{
@@ -100,7 +100,7 @@ member act as director of a domiciliary company, this domiciliary company is the
choices: countryNationalityList(i18n),
required: true,
hide(value, root) {
- return !!root && root["CUSTOMER_INFO_TYPE"] !== "NATURAL_PERSON";
+ return !root || root["CUSTOMER_INFO_TYPE"] !== "NATURAL_PERSON";
},
},
{
@@ -110,7 +110,7 @@ member act as director of a domiciliary company, this domiciliary company is the
accept: "application/pdf",
required: true,
hide(value, root) {
- return !!root && root["CUSTOMER_INFO_TYPE"] !== "NATURAL_PERSON";
+ return !root || root["CUSTOMER_INFO_TYPE"] !== "NATURAL_PERSON";
},
},
],
@@ -119,7 +119,7 @@ member act as director of a domiciliary company, this domiciliary company is the
title: i18n.str`Information on customer (sole proprietor)`,
description: i18n.str`The customer is a sole proprietor`,
hide(root) {
- return !!root && root["CUSTOMER_INFO_TYPE"] !== "NATURAL_PERSON";
+ return !root || root["CUSTOMER_INFO_TYPE"] !== "NATURAL_PERSON";
},
fields: [
{
@@ -128,7 +128,7 @@ member act as director of a domiciliary company, this domiciliary company is the
type: "text",
required: false,
hide(value, root) {
- return !!root && root["CUSTOMER_INFO_TYPE"] !== "NATURAL_PERSON";
+ return !root || root["CUSTOMER_INFO_TYPE"] !== "NATURAL_PERSON";
},
},
{
@@ -138,7 +138,7 @@ member act as director of a domiciliary company, this domiciliary company is the
type: "text",
required: false,
hide(value, root) {
- return !!root && root["CUSTOMER_INFO_TYPE"] !== "NATURAL_PERSON";
+ return !root || root["CUSTOMER_INFO_TYPE"] !== "NATURAL_PERSON";
},
},
{
@@ -149,7 +149,7 @@ member act as director of a domiciliary company, this domiciliary company is the
required: false,
accept: "application/pdf",
hide(value, root) {
- return !!root && root["CUSTOMER_INFO_TYPE"] !== "NATURAL_PERSON";
+ return !root || root["CUSTOMER_INFO_TYPE"] !== "NATURAL_PERSON";
},
},
],
@@ -158,35 +158,16 @@ member act as director of a domiciliary company, this domiciliary company is the
title: i18n.str`Information on customer`,
description: i18n.str`The customer is a legal entity`,
hide(root) {
- return !!root && root["CUSTOMER_INFO_TYPE"] !== "LEGAL_ENTITY";
+ return !root || root["CUSTOMER_INFO_TYPE"] !== "LEGAL_ENTITY";
},
fields: [
- // {
- // id: TalerFormAttributes.VQF_902_1.CUSTOMER_NATURAL_COMPANY_NAME.id,
- // label: i18n.str`Company name`,
- // type: "text",
- // required: false,
- // hide(value, root) {
- // return !!root && root["CUSTOMER_INFO_TYPE"] !== "LEGAL_ENTITY";
- // },
- // },
- // {
- // id: TalerFormAttributes.VQF_902_1.CUSTOMER_NATURAL_REGISTERED_OFFICE
- // .id,
- // label: i18n.str`Registered office`,
- // type: "text",
- // required: false,
- // hide(value, root) {
- // return !!root && root["CUSTOMER_INFO_TYPE"] !== "LEGAL_ENTITY";
- // },
- // },
{
id: TalerFormAttributes.VQF_902_1.CUSTOMER_ENTITY_COMPANY_NAME.id,
label: i18n.str`Company name`,
type: "text",
required: true,
hide(value, root) {
- return !!root && root["CUSTOMER_INFO_TYPE"] !== "LEGAL_ENTITY";
+ return !root || root["CUSTOMER_INFO_TYPE"] !== "LEGAL_ENTITY";
},
},
{
@@ -195,7 +176,7 @@ member act as director of a domiciliary company, this domiciliary company is the
type: "textArea",
required: true,
hide(value, root) {
- return !!root && root["CUSTOMER_INFO_TYPE"] !== "LEGAL_ENTITY";
+ return !root || root["CUSTOMER_INFO_TYPE"] !== "LEGAL_ENTITY";
},
},
{
@@ -205,7 +186,7 @@ member act as director of a domiciliary company, this domiciliary company is the
type: "text",
required: false,
hide(value, root) {
- return !!root && root["CUSTOMER_INFO_TYPE"] !== "LEGAL_ENTITY";
+ return !root || root["CUSTOMER_INFO_TYPE"] !== "LEGAL_ENTITY";
},
},
{
@@ -214,7 +195,7 @@ member act as director of a domiciliary company, this domiciliary company is the
type: "text",
required: false,
hide(value, root) {
- return !!root && root["CUSTOMER_INFO_TYPE"] !== "LEGAL_ENTITY";
+ return !root || root["CUSTOMER_INFO_TYPE"] !== "LEGAL_ENTITY";
},
},
{
@@ -223,7 +204,7 @@ member act as director of a domiciliary company, this domiciliary company is the
type: "text",
required: false,
hide(value, root) {
- return !!root && root["CUSTOMER_INFO_TYPE"] !== "LEGAL_ENTITY";
+ return !root || root["CUSTOMER_INFO_TYPE"] !== "LEGAL_ENTITY";
},
},
{
@@ -233,7 +214,7 @@ member act as director of a domiciliary company, this domiciliary company is the
accept: "application/pdf",
required: true,
hide(value, root) {
- return !!root && root["CUSTOMER_INFO_TYPE"] !== "LEGAL_ENTITY";
+ return !root || root["CUSTOMER_INFO_TYPE"] !== "LEGAL_ENTITY";
},
},
],
@@ -241,14 +222,14 @@ member act as director of a domiciliary company, this domiciliary company is the
{
title: i18n.str`Information on the natural persons who establish the business relationship for legal entities and partnerships`,
description: i18n.str`For legal entities and partnerships the identity of the natural persons who establish the business relationship must be verified.`,
- hide(root) {
- return !!root && root["CUSTOMER_INFO_TYPE"] !== "LEGAL_ENTITY";
- },
fields: [
{
id: TalerFormAttributes.VQF_902_1.FOUNDER_LIST.id,
label: i18n.str`Founders`,
type: "array",
+ hide(value, root) {
+ return !root || root["CUSTOMER_INFO_TYPE"] !== "LEGAL_ENTITY";
+ },
labelFieldId:
TalerFormAttributes.VQF_902_1_founder.FOUNDER_FULL_NAME.id,
fields: [
diff --git a/packages/web-util/src/hooks/useForm.ts b/packages/web-util/src/hooks/useForm.ts
@@ -29,6 +29,7 @@ import {
UIFormElementConfig,
useTranslationContext,
} from "../index.browser.js";
+import e from "express";
export type FormHandler<T> = {
[k in keyof T]?: T[k] extends string
@@ -103,15 +104,11 @@ export function useForm<T>(
const [formValue, formUpdateHandler] =
useState<RecursivePartial<FormValues<T>>>(initialValue);
- const errors = undefinedIfEmpty<FormErrors<T> | undefined>(
- validateRequiredFields(formValue, design, i18n),
- );
-
- const { handler, result } = constructFormHandler(
+ const { handler, result, errors } = constructFormHandler(
design,
formValue,
formUpdateHandler,
- errors,
+ i18n
);
const status = {
@@ -181,90 +178,53 @@ export function undefinedIfEmpty<T extends object | undefined>(
: undefined;
}
-function validateRequiredFields<FormType>(
- formData: object,
- formDesign: FormDesign,
- i18n: InternationalizationAPI,
-): FormErrors<FormType> | undefined {
- let result: FormErrors<FormType> | undefined = undefined;
-
- function checkIfRequiredFieldHasValue(
- formElement: UIFormElementConfig,
- hiddenSection: boolean | undefined,
- ) {
- if (!("id" in formElement)) {
- return;
- }
- const path = formElement.id.split(".");
- const v = getValueFromPath(formData as any, path);
-
- const hidden =
- hiddenSection === true ||
- formElement.hidden === true ||
- (formElement.hide && formElement.hide(v, formData) === true);
+function checkFormFieldIsValid(formElement: UIFormElementConfig, currentValue: string | undefined, i18n: InternationalizationAPI): ErrorAndLabel | undefined {
+ if (!("id" in formElement)) {
+ return undefined;
+ }
- if (hidden) {
- return;
- }
- if (formElement.required && v === undefined) {
- const e: ErrorAndLabel = {
+ if (formElement.required && currentValue === undefined) {
+ return {
+ label: formElement.label as TranslatedString,
+ message: i18n.str`required`,
+ };
+ } else if (formElement.validator) {
+ try {
+ const message = formElement.validator(currentValue as any);
+ if (message !== undefined) {
+ return {
+ label: formElement.label as TranslatedString,
+ message,
+ };
+ }
+ } catch (e) {
+ console.error(e);
+ const message = i18n.str`Validation function failed. Contact developers ${String(
+ e,
+ )}`
+ console.log(message);
+ return {
label: formElement.label as TranslatedString,
- message: i18n.str`required`,
+ message,
};
- result = setValueIntoPath(result, path, e);
- }
- if (formElement.validator) {
- try {
- const message = formElement.validator(v as any);
- if (message !== undefined) {
- const e: ErrorAndLabel = {
- label: formElement.label as TranslatedString,
- message,
- };
- result = setValueIntoPath(result, path, e);
- }
- } catch (e) {
- console.log(
- `Validation function failed. Contact developers ${String(e)}`,
- );
- console.error(e);
- result = setValueIntoPath(
- result,
- path,
- `Validation function failed. Contact developers ${String(e)}`,
- );
- }
}
}
-
- switch (formDesign.type) {
- case "double-column": {
- formDesign.sections.forEach((sec) => {
- const hidden = sec.hide && sec.hide(result);
- sec.fields.forEach((f) => checkIfRequiredFieldHasValue(f, hidden));
- });
- break;
- }
- case "single-column": {
- formDesign.fields.forEach((f) => checkIfRequiredFieldHasValue(f, undefined));
- break;
- }
- default: {
- assertUnreachable(formDesign);
- }
- }
-
- return result;
+ return undefined
}
function constructFormHandler<T>(
design: FormDesign,
formValue: RecursivePartial<FormValues<T>>,
onValueChange: (d: RecursivePartial<FormValues<T>>) => void,
- errors: FormErrors<T> | undefined,
-): { handler: FormHandler<T>; result: FormStatus<T> } {
+ i18n: InternationalizationAPI,
+): {
+ handler: FormHandler<T>;
+ result: FormStatus<T>;
+ errors: FormErrors<T> | undefined;
+} {
let handler: FormHandler<T> = {};
let result = {} as FormStatus<T>;
+ let errors: FormErrors<T> | undefined = undefined;
function createFieldHandler(
formElement: UIFormElementConfig,
@@ -275,21 +235,28 @@ function constructFormHandler<T>(
}
const path = formElement.id.split(".");
- function updater(newValue: unknown) {
- const updated = setValueIntoPath(formValue, path, newValue);
- onValueChange(updated);
- }
-
const currentValue = getValueFromPath<string>(
formValue as any,
path,
undefined,
);
- const currentError = getValueFromPath<ErrorAndLabel>(
- errors as any,
- path,
- undefined,
- );
+
+ // compute prop based un state
+ const hidden =
+ hiddenSection === true ||
+ formElement.hidden === true ||
+ (formElement.hide && formElement.hide(currentValue, result) === true);
+
+ const currentError: ErrorAndLabel | undefined = !hidden ? checkFormFieldIsValid(formElement, currentValue, i18n) : undefined;
+
+ if (currentError !== undefined) {
+ errors = setValueIntoPath(errors, path, currentError);
+ }
+
+ function updater(newValue: unknown) {
+ const updated = setValueIntoPath(formValue, path, newValue);
+ onValueChange(updated);
+ }
const field: UIFieldHandler = {
error: currentError?.message,
@@ -297,15 +264,10 @@ function constructFormHandler<T>(
onChange: updater,
parentRef: result,
};
-
+
+ // FIXME: handler should not be set but we also need to refactor
+ // ui components.
handler = setValueIntoPath(handler, path, field);
-
- // compute prop based un state
- const hidden =
- hiddenSection === true ||
- formElement.hidden === true ||
- (formElement.hide && formElement.hide(field.value, result) === true);
-
if (!hidden) {
result = setValueIntoPath(result, path, field.value);
}
@@ -328,5 +290,5 @@ function constructFormHandler<T>(
}
}
- return { handler, result };
+ return { handler, result, errors };
}