From 5023322f2610f4acacb9fe88baa6bda85e420c98 Mon Sep 17 00:00:00 2001 From: hpierre74 Date: Thu, 20 Jan 2022 00:58:48 +0100 Subject: [PATCH 1/5] feat(libs/form-builder): add conditional fields --- apps/demo/src/app/config.ts | 37 ++++++++------ apps/demo/src/app/extraValidation.ts | 3 +- libs/form-builder/src/lib/formBuilder.tsx | 12 ++++- libs/form-builder/src/lib/types.ts | 9 +++- .../src/lib/utils/conditionalFields.utils.ts | 48 +++++++++++++++++++ .../src/lib/utils/getSchemaInfo.util.ts | 9 +++- 6 files changed, 100 insertions(+), 18 deletions(-) create mode 100644 libs/form-builder/src/lib/utils/conditionalFields.utils.ts diff --git a/apps/demo/src/app/config.ts b/apps/demo/src/app/config.ts index 7dcbdcc..08fb058 100644 --- a/apps/demo/src/app/config.ts +++ b/apps/demo/src/app/config.ts @@ -14,7 +14,19 @@ export const config = { choices: ['male', 'female'] }, type: 'select', - defaultValue: 'male' + defaultValue: '', + dependsOn: [ + { + key: 'email', + callback: 'checkPattern', + value: + '^(([^<>()[\\]\\\\.,;:\\s@"]+(\\.[^<>()[\\]\\\\.,;:\\s@"]+)*)|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$' + }, + { + key: 'lastName', + callback: 'disappearWhenTruthy' + } + ] }, birthdate: { id: 'birthdate', @@ -50,6 +62,12 @@ export const config = { }, title: 'Email', type: 'text', + dependsOn: [ + { + key: 'firstName', + callback: 'disappearWhenTruthy' + } + ], validation: { checkPattern: { key: 'checkPattern', @@ -73,6 +91,7 @@ export const config = { }, title: 'firstName', type: 'text', + dependsOn: ['gender'], validation: { checkPattern: { key: 'checkPattern', @@ -95,6 +114,7 @@ export const config = { }, title: 'lastName', type: 'text', + dependsOn: ['firstName'], validation: { maxLength: { key: 'checkMaxLength', @@ -145,7 +165,7 @@ export const config = { }, steps: { 'register-step-0': { - fieldsById: ['email', 'gender'], + fieldsById: ['email', 'gender', 'firstName', 'lastName'], id: 'register-step-0', meta: { subtitle: 'Email', @@ -167,19 +187,8 @@ export const config = { } }, 'register-step-2': { - fieldsById: ['firstName', 'lastName'], - id: 'register-step-2', - meta: { - subtitle: 'First name and Last name', - title: 'First name and Last name' - }, - submit: { - label: 'Next' - } - }, - 'register-step-3': { fieldsById: ['birthdate'], - id: 'register-step-3', + id: 'register-step-2', meta: { subtitle: 'Birthdate', title: 'Birthdate' diff --git a/apps/demo/src/app/extraValidation.ts b/apps/demo/src/app/extraValidation.ts index 4327f00..f665bf4 100644 --- a/apps/demo/src/app/extraValidation.ts +++ b/apps/demo/src/app/extraValidation.ts @@ -35,5 +35,6 @@ export const extraValidation = { checkForNumber: () => (input: string) => /\d+/g.test(input), isChecked: () => (value?: string | number) => !!value, checkPattern: (value: string) => (input: string) => - new RegExp(value).test(input) + new RegExp(value).test(input), + disappearWhenTruthy: () => (input: string) => !input }; diff --git a/libs/form-builder/src/lib/formBuilder.tsx b/libs/form-builder/src/lib/formBuilder.tsx index a6cebf9..e9568ef 100644 --- a/libs/form-builder/src/lib/formBuilder.tsx +++ b/libs/form-builder/src/lib/formBuilder.tsx @@ -26,6 +26,7 @@ import { SubmitField } from './components/submitField.component'; import { getFieldRules, FieldRules } from './utils/validation.utils'; import { PreviousStepField } from './components/previousStepField.component'; import { FORM_CLASSNAMES } from './constants'; +import { filterDependentsFieldsById } from './utils/conditionalFields.utils'; const EMPTY_OBJECT = {} as const; export interface FormBuilderProps { @@ -78,6 +79,13 @@ export function FormBuilder({ [currentStepIndex, schema, typesAllowed] ); + const filteredFields = filterDependentsFieldsById( + fieldsById, + fields, + getValues, + extraValidation + ); + const validationRulesById = React.useMemo( () => _.reduce( @@ -99,6 +107,8 @@ export function FormBuilder({ [extraValidation, fields, fieldsById] ); + console.log(validationRulesById); + const setFieldValue = React.useCallback( (id: Path, value) => setValue(id, value, { shouldValidate: true, shouldDirty: true }), @@ -139,7 +149,7 @@ export function FormBuilder({ {_.map(stepsById, (stepId) => ( - {_.map(fieldsById, (fieldId) => { + {_.map(filteredFields, (fieldId) => { const { type, id, defaultValue, meta, validation } = fields[fieldId]; diff --git a/libs/form-builder/src/lib/types.ts b/libs/form-builder/src/lib/types.ts index b0b9f2d..4f6b7ed 100644 --- a/libs/form-builder/src/lib/types.ts +++ b/libs/form-builder/src/lib/types.ts @@ -25,11 +25,18 @@ export interface Validations { [key: string]: Validation; } +export interface DependsOnObject { + key: string; + value?: string | number | null | string[] | number[]; + callback: string; +} + export interface FormField { - id: Path; + id: string; title: string; type: string; meta?: FormMeta | undefined; + dependsOn?: Array; validation?: Validations | undefined; defaultValue?: | UnpackNestedValue> diff --git a/libs/form-builder/src/lib/utils/conditionalFields.utils.ts b/libs/form-builder/src/lib/utils/conditionalFields.utils.ts new file mode 100644 index 0000000..c7ab59a --- /dev/null +++ b/libs/form-builder/src/lib/utils/conditionalFields.utils.ts @@ -0,0 +1,48 @@ +import { UseFormGetValues, FieldValues } from 'react-hook-form'; +import { ExtraValidation, FormField, FormFields } from '../types'; + +export const shouldDisplayField = ( + dependsOn: FormField['dependsOn'], + getValues: UseFormGetValues, + extraValidation?: ExtraValidation +) => { + if (!dependsOn) return true; + + const dependsOnConditions = [] as boolean[]; + + dependsOn.forEach((dependRule) => { + if (typeof dependRule === 'string') { + return dependsOnConditions.push(!!getValues(dependRule)); + } + + if (!extraValidation || !extraValidation[dependRule.callback]) { + return dependsOnConditions.push(true); + } + + return dependsOnConditions.push( + !!extraValidation[dependRule.callback](dependRule.value)( + getValues(dependRule.key) + ) + ); + }); + + return dependsOnConditions.filter((value) => !value).length === 0; +}; + +export const filterDependentsFieldsById = ( + fieldsById: string[], + fields: FormFields, + getValues: UseFormGetValues, + extraValidation?: ExtraValidation +) => { + return fieldsById.reduce((acc, fieldId) => { + const { dependsOn } = fields[fieldId]; + if (!dependsOn) { + return [...acc, fieldId]; + } + + return shouldDisplayField(dependsOn, getValues, extraValidation) + ? [...acc, fieldId] + : acc; + }, [] as string[]); +}; diff --git a/libs/form-builder/src/lib/utils/getSchemaInfo.util.ts b/libs/form-builder/src/lib/utils/getSchemaInfo.util.ts index f4afe8d..5a00fa3 100644 --- a/libs/form-builder/src/lib/utils/getSchemaInfo.util.ts +++ b/libs/form-builder/src/lib/utils/getSchemaInfo.util.ts @@ -16,11 +16,18 @@ export const sanitizeFieldsById = ( return typesAllowed.includes(type) && type !== SUBMIT_FIELD_TYPE; }); +export interface SchemaInfo { + fields: FormFields; + fieldsById: string[]; + submitLabel: string; + stepsById: string[]; +} + export const getSchemaInfo = ( schema: FormSchema, typesAllowed: string[], currentStepIndex: number -) => { +): SchemaInfo => { const steps = _.get(schema, 'steps'); const stepsById = _.get(schema, 'stepsById', EMPTY_ARRAY); const stepId = _.get(stepsById, currentStepIndex); From 9321150a9b5d74a29ec8cfecb5ba9ce50e6b93fe Mon Sep 17 00:00:00 2001 From: hpierre74 Date: Thu, 20 Jan 2022 01:33:15 +0100 Subject: [PATCH 2/5] fix: clean --- apps/demo/src/app/config.ts | 40 +++++-------- libs/form-builder/src/lib/formBuilder.tsx | 8 +-- .../src/lib/utils/conditionalFields.utils.ts | 56 +++++++++++++------ 3 files changed, 59 insertions(+), 45 deletions(-) diff --git a/apps/demo/src/app/config.ts b/apps/demo/src/app/config.ts index 08fb058..ecb4f59 100644 --- a/apps/demo/src/app/config.ts +++ b/apps/demo/src/app/config.ts @@ -15,18 +15,14 @@ export const config = { }, type: 'select', defaultValue: '', - dependsOn: [ - { - key: 'email', - callback: 'checkPattern', - value: - '^(([^<>()[\\]\\\\.,;:\\s@"]+(\\.[^<>()[\\]\\\\.,;:\\s@"]+)*)|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$' - }, - { - key: 'lastName', - callback: 'disappearWhenTruthy' + dependsOn: ['email'], + validation: { + required: { + key: 'required', + message: 'Required field', + value: true } - ] + } }, birthdate: { id: 'birthdate', @@ -62,12 +58,6 @@ export const config = { }, title: 'Email', type: 'text', - dependsOn: [ - { - key: 'firstName', - callback: 'disappearWhenTruthy' - } - ], validation: { checkPattern: { key: 'checkPattern', @@ -91,7 +81,7 @@ export const config = { }, title: 'firstName', type: 'text', - dependsOn: ['gender'], + dependsOn: ['gender', 'email'], validation: { checkPattern: { key: 'checkPattern', @@ -114,13 +104,18 @@ export const config = { }, title: 'lastName', type: 'text', - dependsOn: ['firstName'], + dependsOn: ['firstName', 'gender', 'email'], validation: { maxLength: { key: 'checkMaxLength', message: 'Maximum input length', value: 20 }, + minLength: { + key: 'checkMinLength', + message: 'Minimum input length', + value: 2 + }, required: { key: 'required', message: 'Required field', @@ -198,12 +193,7 @@ export const config = { } } }, - stepsById: [ - 'register-step-0', - 'register-step-1', - 'register-step-2', - 'register-step-3' - ] + stepsById: ['register-step-0', 'register-step-1', 'register-step-2'] }, single_step_register: { fields: { diff --git a/libs/form-builder/src/lib/formBuilder.tsx b/libs/form-builder/src/lib/formBuilder.tsx index e9568ef..ef8a7c6 100644 --- a/libs/form-builder/src/lib/formBuilder.tsx +++ b/libs/form-builder/src/lib/formBuilder.tsx @@ -29,6 +29,7 @@ import { FORM_CLASSNAMES } from './constants'; import { filterDependentsFieldsById } from './utils/conditionalFields.utils'; const EMPTY_OBJECT = {} as const; + export interface FormBuilderProps { defaultValues?: DefaultValues; behavior?: keyof ValidationMode; @@ -79,12 +80,13 @@ export function FormBuilder({ [currentStepIndex, schema, typesAllowed] ); - const filteredFields = filterDependentsFieldsById( + const filteredFields = filterDependentsFieldsById({ fieldsById, fields, getValues, + errors, extraValidation - ); + }); const validationRulesById = React.useMemo( () => @@ -107,8 +109,6 @@ export function FormBuilder({ [extraValidation, fields, fieldsById] ); - console.log(validationRulesById); - const setFieldValue = React.useCallback( (id: Path, value) => setValue(id, value, { shouldValidate: true, shouldDirty: true }), diff --git a/libs/form-builder/src/lib/utils/conditionalFields.utils.ts b/libs/form-builder/src/lib/utils/conditionalFields.utils.ts index c7ab59a..81834f8 100644 --- a/libs/form-builder/src/lib/utils/conditionalFields.utils.ts +++ b/libs/form-builder/src/lib/utils/conditionalFields.utils.ts @@ -1,47 +1,71 @@ -import { UseFormGetValues, FieldValues } from 'react-hook-form'; +import { UseFormGetValues, FieldValues, FieldErrors } from 'react-hook-form'; import { ExtraValidation, FormField, FormFields } from '../types'; -export const shouldDisplayField = ( - dependsOn: FormField['dependsOn'], - getValues: UseFormGetValues, - extraValidation?: ExtraValidation -) => { +export interface ShouldDisplayField { + getValues: UseFormGetValues; + extraValidation?: ExtraValidation; + errors: FieldErrors; + dependsOn: FormField['dependsOn']; +} + +export const shouldDisplayField = ({ + dependsOn, + getValues, + extraValidation, + errors +}: ShouldDisplayField) => { if (!dependsOn) return true; const dependsOnConditions = [] as boolean[]; dependsOn.forEach((dependRule) => { if (typeof dependRule === 'string') { - return dependsOnConditions.push(!!getValues(dependRule)); + return dependsOnConditions.push( + !!getValues(dependRule) && !errors[dependRule] + ); } if (!extraValidation || !extraValidation[dependRule.callback]) { - return dependsOnConditions.push(true); + return dependsOnConditions.push(!errors[dependRule.key]); } return dependsOnConditions.push( !!extraValidation[dependRule.callback](dependRule.value)( getValues(dependRule.key) - ) + ) && !errors[dependRule.key] ); }); return dependsOnConditions.filter((value) => !value).length === 0; }; -export const filterDependentsFieldsById = ( - fieldsById: string[], - fields: FormFields, - getValues: UseFormGetValues, - extraValidation?: ExtraValidation -) => { +export interface FilterDependentsFieldsById { + fieldsById: string[]; + fields: FormFields; + getValues: UseFormGetValues; + extraValidation?: ExtraValidation; + errors: FieldErrors; +} + +export const filterDependentsFieldsById = ({ + fieldsById, + fields, + getValues, + extraValidation, + errors +}: FilterDependentsFieldsById) => { return fieldsById.reduce((acc, fieldId) => { const { dependsOn } = fields[fieldId]; if (!dependsOn) { return [...acc, fieldId]; } - return shouldDisplayField(dependsOn, getValues, extraValidation) + return shouldDisplayField({ + dependsOn, + getValues, + extraValidation, + errors + }) ? [...acc, fieldId] : acc; }, [] as string[]); From f22202f905ccfbcb929c0da654bd6ad094d6b7f6 Mon Sep 17 00:00:00 2001 From: hpierre74 Date: Thu, 20 Jan 2022 14:14:09 +0100 Subject: [PATCH 3/5] docs: improve docs --- apps/docsite/docs/form-builder.md | 256 +++++++++++++++++++++++------ apps/docsite/docs/overview.md | 19 ++- documentation/release.md | 4 - libs/form-builder/src/lib/types.ts | 3 +- 4 files changed, 215 insertions(+), 67 deletions(-) diff --git a/apps/docsite/docs/form-builder.md b/apps/docsite/docs/form-builder.md index a96649d..a27d8ba 100644 --- a/apps/docsite/docs/form-builder.md +++ b/apps/docsite/docs/form-builder.md @@ -23,15 +23,77 @@ In order to create a form using this library, you simply need to import the `For You should provide a `schema` with the following structure: +```ts +import { + DeepMap, + DeepPartial, + Path, + PathValue, + UnionLike, + UnpackNestedValue, +} from 'react-hook-form'; + +export interface FormSchema { + fields: { + [FieldId: string]: { + id: string; + title: string; + type: string; + meta?: { + [key: string]: unknown; + }; + dependsOn?: Array< + | string + | { + key: string; + value?: string | number | null | string[] | number[]; + callback: string; + } + >; + validation?: { + [key: string]: { + key: string; + type?: string; + message: string; + value?: unknown; + }; + }; + defaultValue?: + | UnpackNestedValue> + | string + | number + | string[] + | number[] + | Path; + }; + }; + steps: { + [StepId: string]: { + id: string; + fieldsById: string[]; + submit: { + label: string; + }; + meta?: { + [key: string]: unknown; + }; + }; + }; + stepsById: string[]; +} +``` + +See this stripped down example below of a single input form + ```jsx const schema = { fields: { 'some-unique-identifier': { id: 'some-unique-identifier', title: 'First name', - type: 'text' + type: 'text', }, - ... + // ... }, steps: { 'step-foo': { @@ -42,7 +104,7 @@ const schema = { }, }, }, - stepsById: ['step-foo'] + stepsById: ['step-foo'], }; ``` @@ -60,6 +122,37 @@ const dictionary = { Make sure the `dictionary` keys corresponds to your fields types. +--- + +Dictionary components (field components) must accept three props: + +- propRef: the field registered ref +- name: the id of the field element +- onChange: the onChange callback + +Here are all the base props that will be passed to every FormField. + +```ts +export interface FormFieldProps { + id: string; + validation?: Validations; + errors?: ErrorOption; + setFieldValue?: SetFieldValue; + triggerValidationField?: (value: Path) => void; + propRef?: Ref; + disabled?: boolean; + label?: string; + onClick?: (event: any) => void; + isValidating?: boolean; +} +``` + +:::tip + +You can leverage the `meta` field property to pass more values to your field ! + +::: + ### onSubmit The `onSubmit` callback is called when submitting the form, it follows `react-hook-form` API. @@ -203,71 +296,126 @@ This library doesn't provide steps state management by default. You can implemen :bulb: If you are using redux, we have a slice ready for you :point_right: [@bedrockstreaming/form-redux](./form-redux.md) -## FormField - -The FormField specifics should be handled in the `dictionary` components -The FormField has props: - -- id: the unique identifier of the field -- fieldType: the dictionary type of the field -- dictionary: the list of implemented fields -- setFieldValue: a wrapper to the native react-hooks-form function `setValue`, allow to change the field value without making a controlled component -- errors: return from react-hook-form with errors type and message -- validation: get the validation rules from the form config - ## Dictionary -Dictionary components (field components) must accept three props: - -- propRef: the field registered ref -- name: the id of the field element -- onChange: the onChange callback - ## Validation -To do fields validation, we use the native implementation of react-hooks-form. We get access to a `rule` prop that is passed to our (currently controlled) components, which takes an object that can have several rules. +To do fields validation, we use the native implementation of `react-hooks-form`. We leverage it through the validation field property. -When we need more personalization in our validation for a special type of field for example, we need to do 2 things : +When we want to perform a complex or very specific validation, even async, we need to: - Create an object containing the custom validation functions and pass it to the `extraValidation` prop of the form-Builder - Reference those `extraValidation` functions in the schema config ```jsx - const extraValidation = { - 'customValidationFunction1': (valueFromSchema) => fieldValue => doCustomValidationHere(valueFromSchema, fieldValue), - }; +const extraValidation = { + customValidationFunction1: (valueFromSchema) => (fieldValue) => + doCustomValidationHere(valueFromSchema, fieldValue), +}; - const schema = { - fields: { - BIRTHDATE: { - ... - meta: { - ... +const schema = { + fields: { + birthdate: { + // [...] + validation: { + customValidationFunction1: { + // <-- this is a custom validation + key: 'customValidationFunction1', + message: 'forms.register.birthdate.minAgeError', + value: 13, }, - validation: { - customValidationFunction1: { // <-- this is a custom validation - key: 'customValidationFunction1', - message: 'forms.register.birthdate.minAgeError', - value: 13, - }, - required: { // <-- this is a default validation (native to react-hook-form) - key: 'required', - message: 'forms.required.error', - value: true, - }, + required: { + // <-- this is a default validation (native to react-hook-form) + key: 'required', + message: 'forms.required.error', + value: true, }, }, - } - }; + }, + }, +}; - const MyForm = () => ( - - ); +const MyForm = () => ( + +); + +// More info on the official react-hooks-form doc : https://react-hook-form.com/get-started#Applyvalidation +``` + +## ConditionalFields + +You can add a `dependsOn` entry in any of your field schema. + +```ts +export interface FormField { + // [...] + dependsOn?: Array< + | string // an other field id + | { + key: string; // an other field id + value?: string | number | null | string[] | number[]; // any serializable value, works the same way as validation + callback: string; // the extra validation method key + } + >; +} +``` + +### Using strings - // More info on the official react-hooks-form doc : https://react-hook-form.com/get-started#Applyvalidation +When using a string, corresponding to a field id, the form builder will hide the field until those target field ids have been touched and validated. + +```jsx +const schema = { + fields: { + someField: { + id: 'someField', + // ... + }, + myConditionalField: { + id: 'myConditionalField', + dependsOn: [ + { + key: 'someField', + callback: 'customValidationFunction1', + value: 13, + }, + ], + }, + }, +}; +``` + +### Using objects + +Otherwise, when using objects, you can leverage the `extraValidation` functions to apply custom logic. + +```jsx +const extraValidation = { + customValidationFunction1: (valueFromSchema) => (fieldValue) => + doCustomValidationHere(valueFromSchema, fieldValue), +}; + +const schema = { + fields: { + someField: { + id: 'someField', + // ... + }, + myConditionalField: { + id: 'myConditionalField', + dependsOn: [ + { + key: 'someField', + callback: 'customValidationFunction1', + value: 13, + }, + ], + }, + }, +}; ``` diff --git a/apps/docsite/docs/overview.md b/apps/docsite/docs/overview.md index effee1e..cf4f732 100644 --- a/apps/docsite/docs/overview.md +++ b/apps/docsite/docs/overview.md @@ -22,17 +22,22 @@ As we were eager to keep some control over the process, we went with [react-hook Here is a list of features we are supporting :white_check_mark: -:white_check_mark: Form Generation -:white_check_mark: Asynchronous Custom Validation -:white_check_mark: Multi Steps Forms +- :white_check_mark: Form Generation +- :white_check_mark: Asynchronous Custom Validation +- :white_check_mark: Multi Steps Forms +- :white_check_mark: Conditional Fields --- -Here is a list of features we are **not** supporting :x: +Here is a list of features we will **not** support :x: -:x: Form UI Components -:construction: Conditional Steps -:construction: Conditional Fields +- :x: Form UI Components + +--- + +Here is a list of features we will **probably** support :thinking: + +- :construction: Conditional Steps ## Why did we have to make it ? diff --git a/documentation/release.md b/documentation/release.md index 1298ce8..03c3c48 100644 --- a/documentation/release.md +++ b/documentation/release.md @@ -71,7 +71,3 @@ Then in the `workspace.json` file, add the publish target to your workspace vers } } ``` - -## Caveats - -:warning: Changelog are not properly pushed on Github, bug is being investigated in `@jscutlery/semver` diff --git a/libs/form-builder/src/lib/types.ts b/libs/form-builder/src/lib/types.ts index 4f6b7ed..62ff752 100644 --- a/libs/form-builder/src/lib/types.ts +++ b/libs/form-builder/src/lib/types.ts @@ -44,8 +44,7 @@ export interface FormField { | number | string[] | number[] - | Path - | undefined; + | Path; } export interface FormFields { From 604caf83126f37130f271161c0d45b25db7d3af8 Mon Sep 17 00:00:00 2001 From: hpierre74 Date: Sat, 22 Jan 2022 02:17:36 +0100 Subject: [PATCH 4/5] feat: reconfigure schema --- apps/demo/src/app/config.ts | 32 ++- .../examples/with-material-ui/dictionary.ts | 2 + .../dictionary/checkBox.component.tsx | 62 ++++ .../with-material-ui/form.component.tsx | 4 +- .../with-styled-components/dictionary.ts | 6 +- .../dictionary/checkBox.component.tsx | 18 ++ .../dictionary/gender.component.tsx | 0 .../dictionary/select.component.tsx | 28 ++ .../dictionary/styled.js | 2 +- apps/demo/src/app/extraValidation.ts | 7 +- apps/docsite/docs/form-builder.md | 37 ++- libs/form-builder/README.md | 271 ++++++++++++++---- libs/form-builder/src/lib/types.ts | 10 +- .../src/lib/utils/conditionalFields.utils.ts | 28 +- package.json | 2 +- yarn.lock | 8 +- 16 files changed, 426 insertions(+), 91 deletions(-) create mode 100644 apps/demo/src/app/examples/with-material-ui/dictionary/checkBox.component.tsx create mode 100644 apps/demo/src/app/examples/with-styled-components/dictionary/checkBox.component.tsx create mode 100644 apps/demo/src/app/examples/with-styled-components/dictionary/gender.component.tsx create mode 100644 apps/demo/src/app/examples/with-styled-components/dictionary/select.component.tsx diff --git a/apps/demo/src/app/config.ts b/apps/demo/src/app/config.ts index ecb4f59..de7bdb3 100644 --- a/apps/demo/src/app/config.ts +++ b/apps/demo/src/app/config.ts @@ -14,8 +14,10 @@ export const config = { choices: ['male', 'female'] }, type: 'select', - defaultValue: '', - dependsOn: ['email'], + dependsOn: [ + 'email', + { fieldId: 'newsletter', key: 'isFalse', validate: true } + ], validation: { required: { key: 'required', @@ -24,11 +26,27 @@ export const config = { } } }, + newsletter: { + id: 'newsletter', + title: 'Newsletter checkbox', + meta: { + label: 'Agree to receive newsletter', + name: 'newsletter' + }, + type: 'checkbox', + dependsOn: ['email'], + validation: { + required: { + key: 'isChecked', + message: 'Required' + } + } + }, birthdate: { id: 'birthdate', meta: { errorMessage: 'Birth date invalid', - label: 'Birth date', + label: 'Birthdate', name: 'birthdate' }, title: 'birthdate', @@ -160,7 +178,13 @@ export const config = { }, steps: { 'register-step-0': { - fieldsById: ['email', 'gender', 'firstName', 'lastName'], + fieldsById: [ + 'email', + 'newsletter', + 'gender', + 'firstName', + 'lastName' + ], id: 'register-step-0', meta: { subtitle: 'Email', diff --git a/apps/demo/src/app/examples/with-material-ui/dictionary.ts b/apps/demo/src/app/examples/with-material-ui/dictionary.ts index c97a7aa..d535830 100644 --- a/apps/demo/src/app/examples/with-material-ui/dictionary.ts +++ b/apps/demo/src/app/examples/with-material-ui/dictionary.ts @@ -4,12 +4,14 @@ import { DateInput } from './dictionary/date.component'; import { Submit } from './dictionary/submit.component'; import { Select } from './dictionary/select.component'; import { Previous } from './dictionary/previous.component'; +import { Checkbox } from './dictionary/checkBox.component'; export const dictionary = { text: Text, password: Password, date: DateInput, select: Select, + checkbox: Checkbox, submit: Submit, previous: Previous }; diff --git a/apps/demo/src/app/examples/with-material-ui/dictionary/checkBox.component.tsx b/apps/demo/src/app/examples/with-material-ui/dictionary/checkBox.component.tsx new file mode 100644 index 0000000..bb78d50 --- /dev/null +++ b/apps/demo/src/app/examples/with-material-ui/dictionary/checkBox.component.tsx @@ -0,0 +1,62 @@ +import * as React from 'react'; +import { Ref, useMemo } from 'react'; +import { FieldErrors } from 'react-hook-form'; + +import FormGroup from '@mui/material/FormGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import MUICheckbox from '@mui/material/Checkbox'; +import Box from '@mui/material/Box'; + +export const Checkbox = ({ + 'data-testid': dataTestId, + errorMessage, + errors, + id, + label, + name, + onBlur, + onChange, + optionalText, + propRef, + value +}: { + 'data-testid': string; + errorMessage: string; + errors: FieldErrors; + id: string; + label: string; + name: string; + onBlur: (event: any) => void; + onChange: (event: React.ChangeEvent) => void; + optionalText?: string; + propRef: Ref; + type?: string; + value?: boolean; +}) => { + const inputProps = useMemo( + () => ({ ref: propRef, 'aria-label': 'controlled' }), + [propRef] + ); + const error = errors && errors.type && errorMessage; + + return ( + + + + } + /> + + + ); +}; diff --git a/apps/demo/src/app/examples/with-material-ui/form.component.tsx b/apps/demo/src/app/examples/with-material-ui/form.component.tsx index f2fa4f1..2f401e2 100644 --- a/apps/demo/src/app/examples/with-material-ui/form.component.tsx +++ b/apps/demo/src/app/examples/with-material-ui/form.component.tsx @@ -36,7 +36,9 @@ const defaultValues = { firstName: '', lastName: '', birthdate: '', - password: '' + password: '', + gender: '', + newsletter: true }; const { diff --git a/apps/demo/src/app/examples/with-styled-components/dictionary.ts b/apps/demo/src/app/examples/with-styled-components/dictionary.ts index fa42fc1..59be1a4 100644 --- a/apps/demo/src/app/examples/with-styled-components/dictionary.ts +++ b/apps/demo/src/app/examples/with-styled-components/dictionary.ts @@ -3,11 +3,15 @@ import { Password } from './dictionary/password.component'; import { DateInput } from './dictionary/date.component'; import { Submit } from './dictionary/submit.component'; import { Previous } from './dictionary/previous.component'; +import { Checkbox } from './dictionary/checkBox.component'; +import { Select } from './dictionary/select.component'; export const dictionary = { text: Text, password: Password, date: DateInput, submit: Submit, - previous: Previous + previous: Previous, + checkbox: Checkbox, + select: Select }; diff --git a/apps/demo/src/app/examples/with-styled-components/dictionary/checkBox.component.tsx b/apps/demo/src/app/examples/with-styled-components/dictionary/checkBox.component.tsx new file mode 100644 index 0000000..f55e625 --- /dev/null +++ b/apps/demo/src/app/examples/with-styled-components/dictionary/checkBox.component.tsx @@ -0,0 +1,18 @@ +export const Checkbox = ({ + onChange, + value, + label, + ...props +}: { + onChange: (event: React.ChangeEvent) => void; + value: boolean; + label: string; +}) => { + return ( +
+ + + {/* */} +
+ ); +}; diff --git a/apps/demo/src/app/examples/with-styled-components/dictionary/gender.component.tsx b/apps/demo/src/app/examples/with-styled-components/dictionary/gender.component.tsx new file mode 100644 index 0000000..e69de29 diff --git a/apps/demo/src/app/examples/with-styled-components/dictionary/select.component.tsx b/apps/demo/src/app/examples/with-styled-components/dictionary/select.component.tsx new file mode 100644 index 0000000..2d92160 --- /dev/null +++ b/apps/demo/src/app/examples/with-styled-components/dictionary/select.component.tsx @@ -0,0 +1,28 @@ +export const Select = ({ + onChange, + value, + label, + choices, + multiple +}: { + onChange: (event: React.ChangeEvent) => void; + value: string | number; + label: string; + choices: string[] | number[]; + multiple?: boolean; +}) => { + return ( +
+ +
+ ); +}; diff --git a/apps/demo/src/app/examples/with-styled-components/dictionary/styled.js b/apps/demo/src/app/examples/with-styled-components/dictionary/styled.js index 7b12c00..44ccf90 100644 --- a/apps/demo/src/app/examples/with-styled-components/dictionary/styled.js +++ b/apps/demo/src/app/examples/with-styled-components/dictionary/styled.js @@ -2,7 +2,7 @@ import styled from 'styled-components'; export const TextFieldMarginWrapper = styled.div` margin-top: 20px; - margin-bottom: 50px; + margin-bottom: 20px; `; export const TextFieldTopMarginWrapper = styled.div` diff --git a/apps/demo/src/app/extraValidation.ts b/apps/demo/src/app/extraValidation.ts index f665bf4..030475f 100644 --- a/apps/demo/src/app/extraValidation.ts +++ b/apps/demo/src/app/extraValidation.ts @@ -1,6 +1,7 @@ import _ from 'lodash'; import { getUserAge, isDateValid } from '@forms/examples/birthdate'; +import { ExtraValidation } from '@bedrockstreaming/form-builder'; const config = { onboarding: { @@ -33,8 +34,8 @@ export const extraValidation = { checkForUpper: () => (input: string) => /[A-Z]+/g.test(input), checkForLower: () => (input: string) => /[a-z]+/g.test(input), checkForNumber: () => (input: string) => /\d+/g.test(input), - isChecked: () => (value?: string | number) => !!value, + isChecked: () => (value?: boolean) => !!value, checkPattern: (value: string) => (input: string) => new RegExp(value).test(input), - disappearWhenTruthy: () => (input: string) => !input -}; + isFalse: () => (input: boolean) => input === false +} as ExtraValidation; diff --git a/apps/docsite/docs/form-builder.md b/apps/docsite/docs/form-builder.md index a27d8ba..decfed2 100644 --- a/apps/docsite/docs/form-builder.md +++ b/apps/docsite/docs/form-builder.md @@ -124,11 +124,7 @@ Make sure the `dictionary` keys corresponds to your fields types. --- -Dictionary components (field components) must accept three props: - -- propRef: the field registered ref -- name: the id of the field element -- onChange: the onChange callback +Dictionary components (field components) can use some defined props, Here are all the base props that will be passed to every FormField. @@ -347,7 +343,7 @@ const MyForm = () => ( // More info on the official react-hooks-form doc : https://react-hook-form.com/get-started#Applyvalidation ``` -## ConditionalFields +## Conditional Fields You can add a `dependsOn` entry in any of your field schema. @@ -357,9 +353,10 @@ export interface FormField { dependsOn?: Array< | string // an other field id | { - key: string; // an other field id + fieldId: string; // an other field id + key: string; // validation key value?: string | number | null | string[] | number[]; // any serializable value, works the same way as validation - callback: string; // the extra validation method key + validate?: boolean; // perform an extra validation "manually" } >; } @@ -392,30 +389,48 @@ const schema = { ### Using objects -Otherwise, when using objects, you can leverage the `extraValidation` functions to apply custom logic. +Otherwise, when using objects, you can either check for a specific validation error or leverage the `extraValidation` functions to execute a ```jsx const extraValidation = { customValidationFunction1: (valueFromSchema) => (fieldValue) => doCustomValidationHere(valueFromSchema, fieldValue), + customValidationFunction2: (valueFromSchema) => (fieldValue) => + valueFromSchema === fieldValue, }; const schema = { fields: { someField: { id: 'someField', + validation: { + key: 'customValidationFunction2' + value: 'foo', + message: 'Some error message' + } // ... }, myConditionalField: { id: 'myConditionalField', dependsOn: [ { - key: 'someField', - callback: 'customValidationFunction1', + fieldId: 'someField', + key: 'customValidationFunction1', value: 13, + validate: true, + }, + { + fieldId: 'someField', + key: 'customValidationFunction2', }, ], }, }, }; ``` + +:::warning + +When using boolean values, you may need to target the required key following the object form + +::: diff --git a/libs/form-builder/README.md b/libs/form-builder/README.md index cbdd79e..0ba1087 100644 --- a/libs/form-builder/README.md +++ b/libs/form-builder/README.md @@ -20,15 +20,77 @@ In order to create a form using this library, you simply need to import the `For You should provide a `schema` with the following structure: +```ts +import { + DeepMap, + DeepPartial, + Path, + PathValue, + UnionLike, + UnpackNestedValue, +} from 'react-hook-form'; + +export interface FormSchema { + fields: { + [FieldId: string]: { + id: string; + title: string; + type: string; + meta?: { + [key: string]: unknown; + }; + dependsOn?: Array< + | string + | { + key: string; + value?: string | number | null | string[] | number[]; + callback: string; + } + >; + validation?: { + [key: string]: { + key: string; + type?: string; + message: string; + value?: unknown; + }; + }; + defaultValue?: + | UnpackNestedValue> + | string + | number + | string[] + | number[] + | Path; + }; + }; + steps: { + [StepId: string]: { + id: string; + fieldsById: string[]; + submit: { + label: string; + }; + meta?: { + [key: string]: unknown; + }; + }; + }; + stepsById: string[]; +} +``` + +See this stripped down example below of a single input form + ```jsx const schema = { fields: { 'some-unique-identifier': { id: 'some-unique-identifier', title: 'First name', - type: 'text' + type: 'text', }, - ... + // ... }, steps: { 'step-foo': { @@ -39,7 +101,7 @@ const schema = { }, }, }, - stepsById: ['step-foo'] + stepsById: ['step-foo'], }; ``` @@ -57,6 +119,37 @@ const dictionary = { Make sure the `dictionary` keys corresponds to your fields types. +--- + +Dictionary components (field components) must accept three props: + +- propRef: the field registered ref +- name: the id of the field element +- onChange: the onChange callback + +Here are all the base props that will be passed to every FormField. + +```ts +export interface FormFieldProps { + id: string; + validation?: Validations; + errors?: ErrorOption; + setFieldValue?: SetFieldValue; + triggerValidationField?: (value: Path) => void; + propRef?: Ref; + disabled?: boolean; + label?: string; + onClick?: (event: any) => void; + isValidating?: boolean; +} +``` + +:::tip + +You can leverage the `meta` field property to pass more values to your field ! + +::: + ### onSubmit The `onSubmit` callback is called when submitting the form, it follows `react-hook-form` API. @@ -198,75 +291,143 @@ const FormWrapper = () => { This library doesn't provide steps state management by default. You can implement your own step management logic through the `onNextStep` and `onPreviousStep` callbacks, there you can change the `currentStepIndex` prop passed to the `FormBuilder` as it is done in the previous example. -:bulb: If you are using redux, we have a slice ready for you :point_right: [@bedrockstreaming/form-redux](../form-redux/README.md) - -## FormField - -The FormField specifics should be handled in the `dictionary` components -The FormField has props: - -- id: the unique identifier of the field -- fieldType: the dictionary type of the field -- dictionary: the list of implemented fields -- setFieldValue: a wrapper to the native react-hooks-form function `setValue`, allow to change the field value without making a controlled component -- errors: return from react-hook-form with errors type and message -- validation: get the validation rules from the form config +:bulb: If you are using redux, we have a slice ready for you :point_right: [@bedrockstreaming/form-redux](./form-redux.md) ## Dictionary -Dictionary components (field components) must accept three props: - -- propRef: the field registered ref -- name: the id of the field element -- onChange: the onChange callback - ## Validation -To do fields validation, we use the native implementation of react-hooks-form. We get access to a `rule` prop that is passed to our (currently controlled) components, which takes an object that can have several rules. +To do fields validation, we use the native implementation of `react-hooks-form`. We leverage it through the validation field property. -When we need more personalization in our validation for a special type of field for example, we need to do 2 things : +When we want to perform a complex or very specific validation, even async, we need to: - Create an object containing the custom validation functions and pass it to the `extraValidation` prop of the form-Builder - Reference those `extraValidation` functions in the schema config ```jsx - const extraValidation = { - 'customValidationFunction1': (valueFromSchema) => fieldValue => doCustomValidationHere(valueFromSchema, fieldValue), - }; +const extraValidation = { + customValidationFunction1: (valueFromSchema) => (fieldValue) => + doCustomValidationHere(valueFromSchema, fieldValue), +}; - const schema = { - fields: { - birthDate: { - ... - meta: { - ... +const schema = { + fields: { + birthdate: { + // [...] + validation: { + customValidationFunction1: { + // <-- this is a custom validation + key: 'customValidationFunction1', + message: 'forms.register.birthdate.minAgeError', + value: 13, }, - validation: { - customValidationFunction1: { // <-- this is a custom validation - key: 'customValidationFunction1', - message: 'some.translated.message.minAgeError', - value: 13, - }, - required: { // <-- this is a default validation (native to react-hook-form) - key: 'required', - message: 'some.translated.message.requiredError', - value: true, - }, + required: { + // <-- this is a default validation (native to react-hook-form) + key: 'required', + message: 'forms.required.error', + value: true, }, }, - } - }; + }, + }, +}; - const MyForm = () => ( - - ); +const MyForm = () => ( + +); + +// More info on the official react-hooks-form doc : https://react-hook-form.com/get-started#Applyvalidation +``` + +## ConditionalFields + +You can add a `dependsOn` entry in any of your field schema. + +```ts +export interface FormField { + // [...] + dependsOn?: Array< + | string // an other field id + | { + fieldId: string; // an other field id + key: string; // validation key + value?: string | number | null | string[] | number[]; // any serializable value, works the same way as validation + validate?: boolean; // perform an extra validation "manually" + } + >; +} +``` + +### Using strings + +When using a string, corresponding to a field id, the form builder will hide the field until those target field ids have been touched and validated. + +```jsx +const schema = { + fields: { + someField: { + id: 'someField', + // ... + }, + myConditionalField: { + id: 'myConditionalField', + dependsOn: [ + { + key: 'someField', + callback: 'customValidationFunction1', + value: 13, + }, + ], + }, + }, +}; +``` + +### Using objects + +Otherwise, when using objects, you can either check for a specific validation error or leverage the `extraValidation` functions to execute a + +```jsx +const extraValidation = { + customValidationFunction1: (valueFromSchema) => (fieldValue) => + doCustomValidationHere(valueFromSchema, fieldValue), + customValidationFunction2: (valueFromSchema) => (fieldValue) => + valueFromSchema === fieldValue, +}; - // More info on the official react-hooks-form doc : https://react-hook-form.com/get-started#Applyvalidation +const schema = { + fields: { + someField: { + id: 'someField', + validation: { + key: 'customValidationFunction2' + value: 'foo', + message: 'Some error message' + } + // ... + }, + myConditionalField: { + id: 'myConditionalField', + dependsOn: [ + { + fieldId: 'someField', + key: 'customValidationFunction1', + value: 13, + validate: true, + }, + { + fieldId: 'someField', + key: 'customValidationFunction2', + }, + ], + }, + }, +}; ``` ## Examples diff --git a/libs/form-builder/src/lib/types.ts b/libs/form-builder/src/lib/types.ts index 62ff752..dd3d7fa 100644 --- a/libs/form-builder/src/lib/types.ts +++ b/libs/form-builder/src/lib/types.ts @@ -1,14 +1,13 @@ import * as React from 'react'; import { - DeepMap, - DeepPartial, Path, PathValue, - UnionLike, + FieldValues, + FieldNamesMarkedBoolean, UnpackNestedValue } from 'react-hook-form'; -export type DirtyFields = DeepMap>, true>; +export type DirtyFields = FieldNamesMarkedBoolean; export interface FormMeta { [key: string]: unknown; @@ -26,9 +25,10 @@ export interface Validations { } export interface DependsOnObject { + fieldId: string; key: string; value?: string | number | null | string[] | number[]; - callback: string; + validate?: boolean; } export interface FormField { diff --git a/libs/form-builder/src/lib/utils/conditionalFields.utils.ts b/libs/form-builder/src/lib/utils/conditionalFields.utils.ts index 81834f8..d765131 100644 --- a/libs/form-builder/src/lib/utils/conditionalFields.utils.ts +++ b/libs/form-builder/src/lib/utils/conditionalFields.utils.ts @@ -19,20 +19,38 @@ export const shouldDisplayField = ({ const dependsOnConditions = [] as boolean[]; dependsOn.forEach((dependRule) => { + // Validate at field level on string if (typeof dependRule === 'string') { return dependsOnConditions.push( !!getValues(dependRule) && !errors[dependRule] ); } + const fieldValue = getValues(dependRule.fieldId); + const fieldError = errors[dependRule.fieldId]; - if (!extraValidation || !extraValidation[dependRule.callback]) { - return dependsOnConditions.push(!errors[dependRule.key]); + // When the validate option is disabled + // Check for specific validation error + if (!dependRule.validate) { + const validationError = fieldError && fieldError[dependRule.key]; + return dependsOnConditions.push(!!fieldValue && !validationError); } + const validateMethod = extraValidation && extraValidation[dependRule.key]; + + // When validation method is missing from extraValidation, only assert on fieldError + if (!validateMethod) { + return dependsOnConditions.push(!!fieldValue && !fieldError); + } + + console.log( + validateMethod(dependRule.value)(fieldValue), + dependRule, + fieldValue, + fieldError + ); + return dependsOnConditions.push( - !!extraValidation[dependRule.callback](dependRule.value)( - getValues(dependRule.key) - ) && !errors[dependRule.key] + !!validateMethod(dependRule.value)(fieldValue) && !fieldError ); }); diff --git a/package.json b/package.json index d921d0d..105d426 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "prism-react-renderer": "1.2.1", "react": "17.0.2", "react-dom": "17.0.2", - "react-hook-form": "7.19.1", + "react-hook-form": "7.25.0", "react-redux": "7.2.6", "react-router-dom": "5.2.0", "redux": "4.1.2", diff --git a/yarn.lock b/yarn.lock index e02f470..626126b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17027,10 +17027,10 @@ react-helmet@^6.1.0: react-fast-compare "^3.1.1" react-side-effect "^2.1.0" -react-hook-form@7.19.1: - version "7.19.1" - resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.19.1.tgz#df19c065a96cbd4275c4ca8992a9f5908b114a4f" - integrity sha512-e0Oii07qNAa72JeGUT5czVCMwdAFPxmxYvd1Y9oPy2KVD6ZGblN6DG1G7AwL9Bz2lOPFZu15SRNnn0Vpx/eGdg== +react-hook-form@7.25.0: + version "7.25.0" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.25.0.tgz#ffba58e5e14c8eeb81e2c43b741667552f11ae29" + integrity sha512-MyF4YXegIT/vfyZloTm98mpJwLUPfULdX37yPzXeijT1hePCkV8DN1IAnEufxgtqCpc7aFGRinegQwisUGZCnA== react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1: version "16.13.1" From 3b47956c3cb05b725b6212dd819039b9e739376a Mon Sep 17 00:00:00 2001 From: hpierre74 Date: Sat, 22 Jan 2022 20:16:33 +0100 Subject: [PATCH 5/5] refactor: improve example --- apps/demo/src/app/config.ts | 24 +++++++------------ .../dictionary/checkBox.component.tsx | 1 - apps/demo/src/app/extraValidation.ts | 2 +- apps/docsite/docs/form-builder.md | 4 ++-- .../src/lib/utils/conditionalFields.utils.ts | 7 ------ 5 files changed, 12 insertions(+), 26 deletions(-) diff --git a/apps/demo/src/app/config.ts b/apps/demo/src/app/config.ts index de7bdb3..18fad73 100644 --- a/apps/demo/src/app/config.ts +++ b/apps/demo/src/app/config.ts @@ -16,7 +16,7 @@ export const config = { type: 'select', dependsOn: [ 'email', - { fieldId: 'newsletter', key: 'isFalse', validate: true } + { fieldId: 'discloseGender', key: 'isTrue', validate: true } ], validation: { required: { @@ -26,21 +26,15 @@ export const config = { } } }, - newsletter: { - id: 'newsletter', - title: 'Newsletter checkbox', + discloseGender: { + id: 'discloseGender', + title: 'discloseGender checkbox', meta: { - label: 'Agree to receive newsletter', - name: 'newsletter' + label: 'I agree to disclose my gender', + name: 'discloseGender' }, type: 'checkbox', - dependsOn: ['email'], - validation: { - required: { - key: 'isChecked', - message: 'Required' - } - } + dependsOn: ['email'] }, birthdate: { id: 'birthdate', @@ -99,7 +93,7 @@ export const config = { }, title: 'firstName', type: 'text', - dependsOn: ['gender', 'email'], + dependsOn: ['email'], validation: { checkPattern: { key: 'checkPattern', @@ -180,7 +174,7 @@ export const config = { 'register-step-0': { fieldsById: [ 'email', - 'newsletter', + 'discloseGender', 'gender', 'firstName', 'lastName' diff --git a/apps/demo/src/app/examples/with-styled-components/dictionary/checkBox.component.tsx b/apps/demo/src/app/examples/with-styled-components/dictionary/checkBox.component.tsx index f55e625..9062cab 100644 --- a/apps/demo/src/app/examples/with-styled-components/dictionary/checkBox.component.tsx +++ b/apps/demo/src/app/examples/with-styled-components/dictionary/checkBox.component.tsx @@ -12,7 +12,6 @@ export const Checkbox = ({
- {/* */}
); }; diff --git a/apps/demo/src/app/extraValidation.ts b/apps/demo/src/app/extraValidation.ts index 030475f..96f7c1b 100644 --- a/apps/demo/src/app/extraValidation.ts +++ b/apps/demo/src/app/extraValidation.ts @@ -37,5 +37,5 @@ export const extraValidation = { isChecked: () => (value?: boolean) => !!value, checkPattern: (value: string) => (input: string) => new RegExp(value).test(input), - isFalse: () => (input: boolean) => input === false + isTrue: () => (input: boolean) => input === true } as ExtraValidation; diff --git a/apps/docsite/docs/form-builder.md b/apps/docsite/docs/form-builder.md index decfed2..c7c7a6d 100644 --- a/apps/docsite/docs/form-builder.md +++ b/apps/docsite/docs/form-builder.md @@ -429,8 +429,8 @@ const schema = { }; ``` -:::warning +:::tip -When using boolean values, you may need to target the required key following the object form +When using boolean values (e.g. for checkbox), there is no other way than asserting the opposite of the default value to display a conditional field ::: diff --git a/libs/form-builder/src/lib/utils/conditionalFields.utils.ts b/libs/form-builder/src/lib/utils/conditionalFields.utils.ts index d765131..d9f780b 100644 --- a/libs/form-builder/src/lib/utils/conditionalFields.utils.ts +++ b/libs/form-builder/src/lib/utils/conditionalFields.utils.ts @@ -42,13 +42,6 @@ export const shouldDisplayField = ({ return dependsOnConditions.push(!!fieldValue && !fieldError); } - console.log( - validateMethod(dependRule.value)(fieldValue), - dependRule, - fieldValue, - fieldError - ); - return dependsOnConditions.push( !!validateMethod(dependRule.value)(fieldValue) && !fieldError );