From 063b4cdc4d554e830ab93466925995eb20c8270c Mon Sep 17 00:00:00 2001 From: Abdallah Al-Soqatri Date: Fri, 3 May 2024 17:35:19 +0200 Subject: [PATCH 01/17] #2718 feature - raise errors from within fields --- packages/core/src/components/Form.tsx | 20 ++++++++++++++++++- .../core/src/components/fields/ArrayField.tsx | 14 +++++++++++++ .../src/components/fields/BooleanField.tsx | 2 ++ .../components/fields/MultiSchemaField.tsx | 2 ++ .../src/components/fields/ObjectField.tsx | 2 ++ .../src/components/fields/SchemaField.tsx | 5 +++++ .../src/components/fields/StringField.tsx | 2 ++ .../src/components/widgets/AltDateWidget.tsx | 6 +++++- packages/utils/src/types.ts | 3 +++ 9 files changed, 54 insertions(+), 2 deletions(-) diff --git a/packages/core/src/components/Form.tsx b/packages/core/src/components/Form.tsx index 9addaf8c0e..0aa703934d 100644 --- a/packages/core/src/components/Form.tsx +++ b/packages/core/src/components/Form.tsx @@ -33,6 +33,7 @@ import { validationDataMerge, ValidatorType, Experimental_DefaultFormStateBehavior, + FieldError, } from '@rjsf/utils'; import _get from 'lodash/get'; import _isEmpty from 'lodash/isEmpty'; @@ -271,6 +272,7 @@ export default class Form< */ constructor(props: FormProps) { super(props); + this.raiseFieldErrors = this.raiseFieldErrors.bind(this); if (!props.validator) { throw new Error('A validator is required for Form functionality to work'); @@ -417,7 +419,13 @@ export default class Form< if (mustValidate) { const schemaValidation = this.validate(formData, schema, schemaUtils, _retrievedSchema); errors = schemaValidation.errors; - errorSchema = schemaValidation.errorSchema; + // If the schema has changed, we do not merge state.errorSchema. + // Else in the case where it hasn't changed, we merge 'state.errorSchema' with 'schemaValidation.errorSchema.' This done to display the raised field error. + if (isSchemaChanged) { + errorSchema = schemaValidation.errorSchema; + } else { + errorSchema = mergeObjects(this.state?.errorSchema, schemaValidation.errorSchema, true) as ErrorSchema; + } schemaValidationErrors = errors; schemaValidationErrorSchema = errorSchema; } else { @@ -463,6 +471,15 @@ export default class Form< return shouldRender(this, nextProps, nextState); } + raiseFieldErrors(fieldName: string, errors: FieldError[]) { + console.log('this', this); + const { noValidate } = this.props; + if (!noValidate) { + const errorSchema = { [fieldName]: { __errors: errors } } as ErrorSchema; + this.setState({ errorSchema }); + } + } + /** Validates the `formData` against the `schema` using the `altSchemaUtils` (if provided otherwise it uses the * `schemaUtils` in the state), returning the results. * @@ -928,6 +945,7 @@ export default class Form< registry={registry} disabled={disabled} readonly={readonly} + raiseFieldErrors={this.raiseFieldErrors} /> {children ? children : } diff --git a/packages/core/src/components/fields/ArrayField.tsx b/packages/core/src/components/fields/ArrayField.tsx index e94a61f9b3..57967d8f01 100644 --- a/packages/core/src/components/fields/ArrayField.tsx +++ b/packages/core/src/components/fields/ArrayField.tsx @@ -17,6 +17,7 @@ import { TranslatableString, UiSchema, ITEMS_KEY, + FieldError, } from '@rjsf/utils'; import cloneDeep from 'lodash/cloneDeep'; import get from 'lodash/get'; @@ -481,6 +482,7 @@ class ArrayField(uiSchema, globalUiOptions); @@ -587,6 +591,7 @@ class ArrayField ); } @@ -609,6 +614,7 @@ class ArrayField ); } @@ -660,6 +667,7 @@ class ArrayField(uiSchema, globalUiOptions); @@ -687,6 +695,7 @@ class ArrayField ); } @@ -712,6 +721,7 @@ class ArrayField['onFocus']; rawErrors?: string[]; totalItems: number; + raiseFieldErrors: (fieldName: string, error: FieldError[]) => void; }) { const { key, @@ -839,6 +851,7 @@ class ArrayField ), className: 'array-item', diff --git a/packages/core/src/components/fields/BooleanField.tsx b/packages/core/src/components/fields/BooleanField.tsx index 9880d47e54..b5929a1a45 100644 --- a/packages/core/src/components/fields/BooleanField.tsx +++ b/packages/core/src/components/fields/BooleanField.tsx @@ -36,6 +36,7 @@ function BooleanField ); } diff --git a/packages/core/src/components/fields/MultiSchemaField.tsx b/packages/core/src/components/fields/MultiSchemaField.tsx index c58b080424..0f5d59d1c3 100644 --- a/packages/core/src/components/fields/MultiSchemaField.tsx +++ b/packages/core/src/components/fields/MultiSchemaField.tsx @@ -151,6 +151,7 @@ class AnyOfField {optionSchema && <_SchemaField {...this.props} schema={optionSchema} uiSchema={optionUiSchema} />} diff --git a/packages/core/src/components/fields/ObjectField.tsx b/packages/core/src/components/fields/ObjectField.tsx index 107cda80df..e42493ede2 100644 --- a/packages/core/src/components/fields/ObjectField.tsx +++ b/packages/core/src/components/fields/ObjectField.tsx @@ -242,6 +242,7 @@ class ObjectField ), name, diff --git a/packages/core/src/components/fields/SchemaField.tsx b/packages/core/src/components/fields/SchemaField.tsx index 860e2491f5..8175653856 100644 --- a/packages/core/src/components/fields/SchemaField.tsx +++ b/packages/core/src/components/fields/SchemaField.tsx @@ -120,6 +120,7 @@ function SchemaFieldRender(uiSchema, globalUiOptions); @@ -183,6 +184,7 @@ function SchemaFieldRender ); @@ -280,6 +282,7 @@ function SchemaFieldRender schemaUtils.retrieveSchema(isObject(_schema) ? (_schema as S) : ({} as S), formData) @@ -332,6 +336,7 @@ function SchemaFieldRender schemaUtils.retrieveSchema(isObject(_schema) ? (_schema as S) : ({} as S), formData) diff --git a/packages/core/src/components/fields/StringField.tsx b/packages/core/src/components/fields/StringField.tsx index da861071d8..b4156f02bc 100644 --- a/packages/core/src/components/fields/StringField.tsx +++ b/packages/core/src/components/fields/StringField.tsx @@ -32,6 +32,7 @@ function StringField ); } diff --git a/packages/core/src/components/widgets/AltDateWidget.tsx b/packages/core/src/components/widgets/AltDateWidget.tsx index 899a368c54..9b01f58be9 100644 --- a/packages/core/src/components/widgets/AltDateWidget.tsx +++ b/packages/core/src/components/widgets/AltDateWidget.tsx @@ -28,7 +28,7 @@ function readyForChange(state: DateObject) { type DateElementProps = Pick< WidgetProps, - 'value' | 'name' | 'disabled' | 'readonly' | 'autofocus' | 'registry' | 'onBlur' | 'onFocus' + 'value' | 'name' | 'disabled' | 'readonly' | 'autofocus' | 'registry' | 'onBlur' | 'onFocus' | 'raiseFieldErrors' > & { rootId: string; select: (property: keyof DateObject, value: any) => void; @@ -49,6 +49,7 @@ function DateElement) { const id = rootId + '_' + type; const { SelectWidget } = registry.widgets; @@ -70,6 +71,7 @@ function DateElement(rootId)} + raiseFieldErrors={raiseFieldErrors} /> ); } @@ -90,6 +92,7 @@ function AltDateWidget) { const { translateString } = registry; const [lastValue, setLastValue] = useState(value); @@ -156,6 +159,7 @@ function AltDateWidget ))} diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index 4343be2353..ef5dd4e2ca 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -401,6 +401,7 @@ export interface FieldProps; + raiseFieldErrors: (fieldName: string, errors: FieldError[]) => void; } /** The definition of a React-based Field component */ @@ -466,6 +467,7 @@ export type FieldTemplateProps () => void; /** The `registry` object */ registry: Registry; + raiseFieldErrors: (fieldName: string, errors: FieldError[]) => void; }; /** The properties that are passed to the `UnsupportedFieldTemplate` implementation */ @@ -757,6 +759,7 @@ export interface WidgetProps; + raiseFieldErrors: (fieldName: string, errors: FieldError[]) => void; } /** The definition of a React-based Widget component */ From e5f05c7c09c58ff8f776c524020f0a8953fcdea8 Mon Sep 17 00:00:00 2001 From: Abdallah Al-Soqatri Date: Thu, 9 May 2024 02:11:20 +0200 Subject: [PATCH 02/17] fixed failing tests --- .../__snapshots__/ArraySnap.test.jsx.snap | 23 +++++++++++++++++++ .../test/__snapshots__/FormSnap.test.jsx.snap | 21 +++++++++++++++++ .../__snapshots__/ObjectSnap.test.jsx.snap | 19 +++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/packages/core/test/__snapshots__/ArraySnap.test.jsx.snap b/packages/core/test/__snapshots__/ArraySnap.test.jsx.snap index 428f12a63e..aba963502c 100644 --- a/packages/core/test/__snapshots__/ArraySnap.test.jsx.snap +++ b/packages/core/test/__snapshots__/ArraySnap.test.jsx.snap @@ -86,6 +86,7 @@ exports[`array fields array icons 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -205,6 +206,7 @@ exports[`array fields array icons 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -422,6 +424,7 @@ exports[`array fields empty errors array 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -479,6 +482,7 @@ exports[`array fields fixed array 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -508,6 +512,7 @@ exports[`array fields fixed array 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={true} step="any" @@ -587,6 +592,7 @@ exports[`array fields has errors 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -652,6 +658,7 @@ exports[`array fields no errors 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -797,6 +804,7 @@ exports[`with title and description array icons 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -933,6 +941,7 @@ exports[`with title and description array icons 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -1194,6 +1203,7 @@ exports[`with title and description fixed array 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -1240,6 +1250,7 @@ exports[`with title and description fixed array 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={true} step="any" @@ -1389,6 +1400,7 @@ exports[`with title and description from both array icons 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -1525,6 +1537,7 @@ exports[`with title and description from both array icons 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -1786,6 +1799,7 @@ exports[`with title and description from both fixed array 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -1832,6 +1846,7 @@ exports[`with title and description from both fixed array 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={true} step="any" @@ -1981,6 +1996,7 @@ exports[`with title and description from uiSchema array icons 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -2117,6 +2133,7 @@ exports[`with title and description from uiSchema array icons 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -2378,6 +2395,7 @@ exports[`with title and description from uiSchema fixed array 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -2424,6 +2442,7 @@ exports[`with title and description from uiSchema fixed array 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={true} step="any" @@ -2534,6 +2553,7 @@ exports[`with title and description with global label off array icons 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -2653,6 +2673,7 @@ exports[`with title and description with global label off array icons 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -2886,6 +2907,7 @@ exports[`with title and description with global label off fixed array 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -2915,6 +2937,7 @@ exports[`with title and description with global label off fixed array 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={true} step="any" diff --git a/packages/core/test/__snapshots__/FormSnap.test.jsx.snap b/packages/core/test/__snapshots__/FormSnap.test.jsx.snap index 2f5dc571f7..e117ada9ac 100644 --- a/packages/core/test/__snapshots__/FormSnap.test.jsx.snap +++ b/packages/core/test/__snapshots__/FormSnap.test.jsx.snap @@ -254,6 +254,7 @@ exports[`single fields field with description 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -313,6 +314,7 @@ exports[`single fields field with description in uiSchema 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -354,6 +356,7 @@ exports[`single fields format color 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} type="color" value="" @@ -392,6 +395,7 @@ exports[`single fields format date 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} type="date" value="" @@ -430,6 +434,7 @@ exports[`single fields format datetime 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} type="datetime-local" value="" @@ -468,6 +473,7 @@ exports[`single fields format time 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} type="time" value="" @@ -528,6 +534,7 @@ exports[`single fields help and error display 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} type="text" value="" @@ -620,6 +627,7 @@ exports[`single fields hidden label 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} type="text" value="" @@ -679,6 +687,7 @@ exports[`single fields number field 0 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} step="any" type="number" @@ -718,6 +727,7 @@ exports[`single fields number field 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} step="any" type="number" @@ -757,6 +767,7 @@ exports[`single fields password field 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} type="password" value="" @@ -871,6 +882,7 @@ exports[`single fields schema examples 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} type="text" value="" @@ -1314,6 +1326,7 @@ exports[`single fields slider field 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} type="range" value={75} @@ -1359,6 +1372,7 @@ exports[`single fields string field format data-url 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} type="file" value="" @@ -1398,6 +1412,7 @@ exports[`single fields string field format email 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} type="email" value="" @@ -1436,6 +1451,7 @@ exports[`single fields string field format uri 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} type="url" value="" @@ -1474,6 +1490,7 @@ exports[`single fields string field regular 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} type="text" value="" @@ -1512,6 +1529,7 @@ exports[`single fields string field with placeholder 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="placeholder" + raiseFieldErrors={[Function]} readOnly={false} type="text" value="" @@ -1603,6 +1621,7 @@ exports[`single fields title field 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -1686,6 +1705,7 @@ exports[`single fields up/down field 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} type="number" value="" @@ -1724,6 +1744,7 @@ exports[`single fields using custom tagName 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} type="text" value="" diff --git a/packages/core/test/__snapshots__/ObjectSnap.test.jsx.snap b/packages/core/test/__snapshots__/ObjectSnap.test.jsx.snap index eaab827dfd..2cb7c2c39f 100644 --- a/packages/core/test/__snapshots__/ObjectSnap.test.jsx.snap +++ b/packages/core/test/__snapshots__/ObjectSnap.test.jsx.snap @@ -60,6 +60,7 @@ exports[`object fields additionalProperties 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -154,6 +155,7 @@ exports[`object fields object 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -181,6 +183,7 @@ exports[`object fields object 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} step="any" @@ -262,6 +265,7 @@ exports[`object fields show add button and fields if additionalProperties is tru onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -394,6 +398,7 @@ exports[`object fields with title and description additionalProperties 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -526,6 +531,7 @@ exports[`object fields with title and description from both additionalProperties onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -637,6 +643,7 @@ exports[`object fields with title and description from both object 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -670,6 +677,7 @@ exports[`object fields with title and description from both object 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} step="any" @@ -762,6 +770,7 @@ exports[`object fields with title and description from uiSchema additionalProper onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -873,6 +882,7 @@ exports[`object fields with title and description from uiSchema object 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -906,6 +916,7 @@ exports[`object fields with title and description from uiSchema object 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} step="any" @@ -998,6 +1009,7 @@ exports[`object fields with title and description from uiSchema show add button onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -1109,6 +1121,7 @@ exports[`object fields with title and description object 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -1142,6 +1155,7 @@ exports[`object fields with title and description object 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} step="any" @@ -1234,6 +1248,7 @@ exports[`object fields with title and description show add button and fields if onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -1349,6 +1364,7 @@ exports[`object fields with title and description with global label off addition onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -1437,6 +1453,7 @@ exports[`object fields with title and description with global label off object 1 onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -1458,6 +1475,7 @@ exports[`object fields with title and description with global label off object 1 onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} step="any" @@ -1533,6 +1551,7 @@ exports[`object fields with title and description with global label off show add onChange={[Function]} onFocus={[Function]} placeholder="" + raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" From 1f4b73f8c263fda0bd38e6cf192bb0133318be2d Mon Sep 17 00:00:00 2001 From: Abdallah Al-Soqatri Date: Thu, 9 May 2024 02:46:02 +0200 Subject: [PATCH 03/17] Fixed failing build --- packages/utils/src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index ef5dd4e2ca..5aaab064da 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -759,7 +759,7 @@ export interface WidgetProps; - raiseFieldErrors: (fieldName: string, errors: FieldError[]) => void; + raiseFieldErrors?: (fieldName: string, errors: FieldError[]) => void; } /** The definition of a React-based Widget component */ From 7c765ad5ab89a7defeb12dcaf87756860b8b9926 Mon Sep 17 00:00:00 2001 From: Abdallah Al-Soqatri Date: Fri, 24 May 2024 17:24:41 +0200 Subject: [PATCH 04/17] Removing raiseError message and errorSchema is updated now using the onChange. --- packages/core/src/components/Form.tsx | 21 +++++++------------ .../core/src/components/fields/ArrayField.tsx | 14 ------------- .../src/components/fields/BooleanField.tsx | 2 -- .../components/fields/MultiSchemaField.tsx | 2 -- .../src/components/fields/ObjectField.tsx | 2 -- .../src/components/fields/SchemaField.tsx | 5 ----- .../src/components/fields/StringField.tsx | 2 -- .../src/components/widgets/AltDateWidget.tsx | 6 +----- packages/utils/src/types.ts | 4 +--- 9 files changed, 10 insertions(+), 48 deletions(-) diff --git a/packages/core/src/components/Form.tsx b/packages/core/src/components/Form.tsx index 0aa703934d..51677e356c 100644 --- a/packages/core/src/components/Form.tsx +++ b/packages/core/src/components/Form.tsx @@ -33,7 +33,6 @@ import { validationDataMerge, ValidatorType, Experimental_DefaultFormStateBehavior, - FieldError, } from '@rjsf/utils'; import _get from 'lodash/get'; import _isEmpty from 'lodash/isEmpty'; @@ -272,7 +271,6 @@ export default class Form< */ constructor(props: FormProps) { super(props); - this.raiseFieldErrors = this.raiseFieldErrors.bind(this); if (!props.validator) { throw new Error('A validator is required for Form functionality to work'); @@ -424,7 +422,11 @@ export default class Form< if (isSchemaChanged) { errorSchema = schemaValidation.errorSchema; } else { - errorSchema = mergeObjects(this.state?.errorSchema, schemaValidation.errorSchema, true) as ErrorSchema; + errorSchema = mergeObjects( + this.state?.errorSchema, + schemaValidation.errorSchema, + 'preventDuplicates' + ) as ErrorSchema; } schemaValidationErrors = errors; schemaValidationErrorSchema = errorSchema; @@ -471,15 +473,6 @@ export default class Form< return shouldRender(this, nextProps, nextState); } - raiseFieldErrors(fieldName: string, errors: FieldError[]) { - console.log('this', this); - const { noValidate } = this.props; - if (!noValidate) { - const errorSchema = { [fieldName]: { __errors: errors } } as ErrorSchema; - this.setState({ errorSchema }); - } - } - /** Validates the `formData` against the `schema` using the `altSchemaUtils` (if provided otherwise it uses the * `schemaUtils` in the state), returning the results. * @@ -628,6 +621,9 @@ export default class Form< errorSchema = merged.errorSchema; errors = merged.errors; } + if (newErrorSchema) { + errorSchema = mergeObjects(errorSchema, newErrorSchema) as ErrorSchema; + } state = { formData: newFormData, errors, @@ -945,7 +941,6 @@ export default class Form< registry={registry} disabled={disabled} readonly={readonly} - raiseFieldErrors={this.raiseFieldErrors} /> {children ? children : } diff --git a/packages/core/src/components/fields/ArrayField.tsx b/packages/core/src/components/fields/ArrayField.tsx index 57967d8f01..e94a61f9b3 100644 --- a/packages/core/src/components/fields/ArrayField.tsx +++ b/packages/core/src/components/fields/ArrayField.tsx @@ -17,7 +17,6 @@ import { TranslatableString, UiSchema, ITEMS_KEY, - FieldError, } from '@rjsf/utils'; import cloneDeep from 'lodash/cloneDeep'; import get from 'lodash/get'; @@ -482,7 +481,6 @@ class ArrayField(uiSchema, globalUiOptions); @@ -591,7 +587,6 @@ class ArrayField ); } @@ -614,7 +609,6 @@ class ArrayField ); } @@ -667,7 +660,6 @@ class ArrayField(uiSchema, globalUiOptions); @@ -695,7 +687,6 @@ class ArrayField ); } @@ -721,7 +712,6 @@ class ArrayField['onFocus']; rawErrors?: string[]; totalItems: number; - raiseFieldErrors: (fieldName: string, error: FieldError[]) => void; }) { const { key, @@ -851,7 +839,6 @@ class ArrayField ), className: 'array-item', diff --git a/packages/core/src/components/fields/BooleanField.tsx b/packages/core/src/components/fields/BooleanField.tsx index b5929a1a45..9880d47e54 100644 --- a/packages/core/src/components/fields/BooleanField.tsx +++ b/packages/core/src/components/fields/BooleanField.tsx @@ -36,7 +36,6 @@ function BooleanField ); } diff --git a/packages/core/src/components/fields/MultiSchemaField.tsx b/packages/core/src/components/fields/MultiSchemaField.tsx index 0f5d59d1c3..c58b080424 100644 --- a/packages/core/src/components/fields/MultiSchemaField.tsx +++ b/packages/core/src/components/fields/MultiSchemaField.tsx @@ -151,7 +151,6 @@ class AnyOfField {optionSchema && <_SchemaField {...this.props} schema={optionSchema} uiSchema={optionUiSchema} />} diff --git a/packages/core/src/components/fields/ObjectField.tsx b/packages/core/src/components/fields/ObjectField.tsx index e42493ede2..107cda80df 100644 --- a/packages/core/src/components/fields/ObjectField.tsx +++ b/packages/core/src/components/fields/ObjectField.tsx @@ -242,7 +242,6 @@ class ObjectField ), name, diff --git a/packages/core/src/components/fields/SchemaField.tsx b/packages/core/src/components/fields/SchemaField.tsx index 8175653856..860e2491f5 100644 --- a/packages/core/src/components/fields/SchemaField.tsx +++ b/packages/core/src/components/fields/SchemaField.tsx @@ -120,7 +120,6 @@ function SchemaFieldRender(uiSchema, globalUiOptions); @@ -184,7 +183,6 @@ function SchemaFieldRender ); @@ -282,7 +280,6 @@ function SchemaFieldRender schemaUtils.retrieveSchema(isObject(_schema) ? (_schema as S) : ({} as S), formData) @@ -336,7 +332,6 @@ function SchemaFieldRender schemaUtils.retrieveSchema(isObject(_schema) ? (_schema as S) : ({} as S), formData) diff --git a/packages/core/src/components/fields/StringField.tsx b/packages/core/src/components/fields/StringField.tsx index b4156f02bc..da861071d8 100644 --- a/packages/core/src/components/fields/StringField.tsx +++ b/packages/core/src/components/fields/StringField.tsx @@ -32,7 +32,6 @@ function StringField ); } diff --git a/packages/core/src/components/widgets/AltDateWidget.tsx b/packages/core/src/components/widgets/AltDateWidget.tsx index 9b01f58be9..899a368c54 100644 --- a/packages/core/src/components/widgets/AltDateWidget.tsx +++ b/packages/core/src/components/widgets/AltDateWidget.tsx @@ -28,7 +28,7 @@ function readyForChange(state: DateObject) { type DateElementProps = Pick< WidgetProps, - 'value' | 'name' | 'disabled' | 'readonly' | 'autofocus' | 'registry' | 'onBlur' | 'onFocus' | 'raiseFieldErrors' + 'value' | 'name' | 'disabled' | 'readonly' | 'autofocus' | 'registry' | 'onBlur' | 'onFocus' > & { rootId: string; select: (property: keyof DateObject, value: any) => void; @@ -49,7 +49,6 @@ function DateElement) { const id = rootId + '_' + type; const { SelectWidget } = registry.widgets; @@ -71,7 +70,6 @@ function DateElement(rootId)} - raiseFieldErrors={raiseFieldErrors} /> ); } @@ -92,7 +90,6 @@ function AltDateWidget) { const { translateString } = registry; const [lastValue, setLastValue] = useState(value); @@ -159,7 +156,6 @@ function AltDateWidget ))} diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index 5aaab064da..f2af62d550 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -401,7 +401,6 @@ export interface FieldProps; - raiseFieldErrors: (fieldName: string, errors: FieldError[]) => void; } /** The definition of a React-based Field component */ @@ -467,7 +466,6 @@ export type FieldTemplateProps () => void; /** The `registry` object */ registry: Registry; - raiseFieldErrors: (fieldName: string, errors: FieldError[]) => void; }; /** The properties that are passed to the `UnsupportedFieldTemplate` implementation */ @@ -744,7 +742,7 @@ export interface WidgetProps void; /** The value change event handler; call it with the new value every time it changes */ - onChange: (value: any) => void; + onChange: (value: any, es?: ErrorSchema, id?: string) => void; /** The input focus event handler; call it with the widget id and value */ onFocus: (id: string, value: any) => void; /** The computed label for this widget, as a string */ From 56bc85c37c7a383bd4af315943a7af60ab2234ee Mon Sep 17 00:00:00 2001 From: Abdallah Al-Soqatri Date: Sat, 13 Jul 2024 17:27:43 +0200 Subject: [PATCH 05/17] reverting tests --- .../__snapshots__/ArraySnap.test.jsx.snap | 23 ------------------- .../test/__snapshots__/FormSnap.test.jsx.snap | 21 ----------------- .../__snapshots__/ObjectSnap.test.jsx.snap | 19 --------------- 3 files changed, 63 deletions(-) diff --git a/packages/core/test/__snapshots__/ArraySnap.test.jsx.snap b/packages/core/test/__snapshots__/ArraySnap.test.jsx.snap index aba963502c..428f12a63e 100644 --- a/packages/core/test/__snapshots__/ArraySnap.test.jsx.snap +++ b/packages/core/test/__snapshots__/ArraySnap.test.jsx.snap @@ -86,7 +86,6 @@ exports[`array fields array icons 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -206,7 +205,6 @@ exports[`array fields array icons 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -424,7 +422,6 @@ exports[`array fields empty errors array 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -482,7 +479,6 @@ exports[`array fields fixed array 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -512,7 +508,6 @@ exports[`array fields fixed array 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={true} step="any" @@ -592,7 +587,6 @@ exports[`array fields has errors 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -658,7 +652,6 @@ exports[`array fields no errors 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -804,7 +797,6 @@ exports[`with title and description array icons 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -941,7 +933,6 @@ exports[`with title and description array icons 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -1203,7 +1194,6 @@ exports[`with title and description fixed array 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -1250,7 +1240,6 @@ exports[`with title and description fixed array 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={true} step="any" @@ -1400,7 +1389,6 @@ exports[`with title and description from both array icons 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -1537,7 +1525,6 @@ exports[`with title and description from both array icons 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -1799,7 +1786,6 @@ exports[`with title and description from both fixed array 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -1846,7 +1832,6 @@ exports[`with title and description from both fixed array 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={true} step="any" @@ -1996,7 +1981,6 @@ exports[`with title and description from uiSchema array icons 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -2133,7 +2117,6 @@ exports[`with title and description from uiSchema array icons 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -2395,7 +2378,6 @@ exports[`with title and description from uiSchema fixed array 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -2442,7 +2424,6 @@ exports[`with title and description from uiSchema fixed array 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={true} step="any" @@ -2553,7 +2534,6 @@ exports[`with title and description with global label off array icons 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -2673,7 +2653,6 @@ exports[`with title and description with global label off array icons 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -2907,7 +2886,6 @@ exports[`with title and description with global label off fixed array 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={true} type="text" @@ -2937,7 +2915,6 @@ exports[`with title and description with global label off fixed array 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={true} step="any" diff --git a/packages/core/test/__snapshots__/FormSnap.test.jsx.snap b/packages/core/test/__snapshots__/FormSnap.test.jsx.snap index a9b2f8ca12..2bc49762b8 100644 --- a/packages/core/test/__snapshots__/FormSnap.test.jsx.snap +++ b/packages/core/test/__snapshots__/FormSnap.test.jsx.snap @@ -254,7 +254,6 @@ exports[`single fields field with description 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -314,7 +313,6 @@ exports[`single fields field with description in uiSchema 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -356,7 +354,6 @@ exports[`single fields format color 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} type="color" value="" @@ -395,7 +392,6 @@ exports[`single fields format date 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} type="date" value="" @@ -434,7 +430,6 @@ exports[`single fields format datetime 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} type="datetime-local" value="" @@ -473,7 +468,6 @@ exports[`single fields format time 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} type="time" value="" @@ -534,7 +528,6 @@ exports[`single fields help and error display 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} type="text" value="" @@ -627,7 +620,6 @@ exports[`single fields hidden label 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} type="text" value="" @@ -687,7 +679,6 @@ exports[`single fields number field 0 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} step="any" type="number" @@ -727,7 +718,6 @@ exports[`single fields number field 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} step="any" type="number" @@ -767,7 +757,6 @@ exports[`single fields password field 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} type="password" value="" @@ -882,7 +871,6 @@ exports[`single fields schema examples 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} type="text" value="" @@ -1524,7 +1512,6 @@ exports[`single fields slider field 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} type="range" value={75} @@ -1570,7 +1557,6 @@ exports[`single fields string field format data-url 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} type="file" value="" @@ -1610,7 +1596,6 @@ exports[`single fields string field format email 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} type="email" value="" @@ -1649,7 +1634,6 @@ exports[`single fields string field format uri 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} type="url" value="" @@ -1688,7 +1672,6 @@ exports[`single fields string field regular 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} type="text" value="" @@ -1727,7 +1710,6 @@ exports[`single fields string field with placeholder 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="placeholder" - raiseFieldErrors={[Function]} readOnly={false} type="text" value="" @@ -1819,7 +1801,6 @@ exports[`single fields title field 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -1903,7 +1884,6 @@ exports[`single fields up/down field 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} type="number" value="" @@ -1942,7 +1922,6 @@ exports[`single fields using custom tagName 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} type="text" value="" diff --git a/packages/core/test/__snapshots__/ObjectSnap.test.jsx.snap b/packages/core/test/__snapshots__/ObjectSnap.test.jsx.snap index 2cb7c2c39f..eaab827dfd 100644 --- a/packages/core/test/__snapshots__/ObjectSnap.test.jsx.snap +++ b/packages/core/test/__snapshots__/ObjectSnap.test.jsx.snap @@ -60,7 +60,6 @@ exports[`object fields additionalProperties 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -155,7 +154,6 @@ exports[`object fields object 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -183,7 +181,6 @@ exports[`object fields object 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} step="any" @@ -265,7 +262,6 @@ exports[`object fields show add button and fields if additionalProperties is tru onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -398,7 +394,6 @@ exports[`object fields with title and description additionalProperties 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -531,7 +526,6 @@ exports[`object fields with title and description from both additionalProperties onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -643,7 +637,6 @@ exports[`object fields with title and description from both object 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -677,7 +670,6 @@ exports[`object fields with title and description from both object 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} step="any" @@ -770,7 +762,6 @@ exports[`object fields with title and description from uiSchema additionalProper onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -882,7 +873,6 @@ exports[`object fields with title and description from uiSchema object 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -916,7 +906,6 @@ exports[`object fields with title and description from uiSchema object 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} step="any" @@ -1009,7 +998,6 @@ exports[`object fields with title and description from uiSchema show add button onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -1121,7 +1109,6 @@ exports[`object fields with title and description object 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -1155,7 +1142,6 @@ exports[`object fields with title and description object 1`] = ` onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} step="any" @@ -1248,7 +1234,6 @@ exports[`object fields with title and description show add button and fields if onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -1364,7 +1349,6 @@ exports[`object fields with title and description with global label off addition onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -1453,7 +1437,6 @@ exports[`object fields with title and description with global label off object 1 onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" @@ -1475,7 +1458,6 @@ exports[`object fields with title and description with global label off object 1 onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} step="any" @@ -1551,7 +1533,6 @@ exports[`object fields with title and description with global label off show add onChange={[Function]} onFocus={[Function]} placeholder="" - raiseFieldErrors={[Function]} readOnly={false} required={false} type="text" From 876cda6aef3c50ffc778894c0f8dd79807e2c179 Mon Sep 17 00:00:00 2001 From: Abdallah Al-Soqatri Date: Sat, 13 Jul 2024 21:27:16 +0200 Subject: [PATCH 06/17] Filtering errors based on your retrieved schema to only show errors for properties in the selected branch. --- packages/core/src/components/Form.tsx | 26 +++++++++++++++++++++++++- packages/utils/src/types.ts | 1 - 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/packages/core/src/components/Form.tsx b/packages/core/src/components/Form.tsx index 94abe15937..7c433178d1 100644 --- a/packages/core/src/components/Form.tsx +++ b/packages/core/src/components/Form.tsx @@ -585,12 +585,34 @@ export default class Form< omitExtraData = (formData?: T): T | undefined => { const { schema, schemaUtils } = this.state; const retrievedSchema = schemaUtils.retrieveSchema(schema, formData); + const pathSchema = schemaUtils.toPathSchema(retrievedSchema, '', formData); const fieldNames = this.getFieldNames(pathSchema, formData); const newFormData = this.getUsedFormData(formData, fieldNames); return newFormData; }; + // Filtering errors based on your retrieved schema to only show errors for properties in the selected branch. + private filterErrorsBasedOnSchema(schemaErrors: ErrorSchema, resolvedSchema?: S, formData?: any): ErrorSchema { + const { retrievedSchema, schemaUtils } = this.state; + const _retrievedSchema = resolvedSchema ?? retrievedSchema; + const pathSchema = schemaUtils.toPathSchema(_retrievedSchema, '', formData); + const fieldNames = this.getFieldNames(pathSchema, formData); + const filteredErrors: ErrorSchema = _pick(schemaErrors, fieldNames as unknown as string[]); + // Removing undefined and empty errors. + const filterUndefinedErrors = (errors: ErrorSchema): ErrorSchema => { + Object.keys(errors).forEach((key) => { + if (errors[key] === undefined) { + delete errors[key]; + } else if (typeof errors[key] === 'object' && !Array.isArray(errors[key].__errors)) { + filterUndefinedErrors(errors[key]); + } + }); + return errors; + }; + return filterUndefinedErrors(filteredErrors); + } + /** Function to handle changes made to a field in the `Form`. This handler receives an entirely new copy of the * `formData` along with a new `ErrorSchema`. It will first update the `formData` with any missing default fields and * then, if `omitExtraData` and `liveOmit` are turned on, the `formData` will be filtered to remove any extra data not @@ -634,8 +656,10 @@ export default class Form< errorSchema = merged.errorSchema; errors = merged.errors; } + // Merging 'newErrorSchema' into 'errorSchema' to display the custom raised errors. if (newErrorSchema) { - errorSchema = mergeObjects(errorSchema, newErrorSchema) as ErrorSchema; + const filteredErrors = this.filterErrorsBasedOnSchema(newErrorSchema, retrievedSchema, newFormData); + errorSchema = mergeObjects(errorSchema, filteredErrors, 'preventDuplicates') as ErrorSchema; } state = { formData: newFormData, diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index 748485ba6e..8e3ea66713 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -765,7 +765,6 @@ export interface WidgetProps; - raiseFieldErrors?: (fieldName: string, errors: FieldError[]) => void; } /** The definition of a React-based Widget component */ From 13da8abdb7d91b7792965e4244d95e673ede9644 Mon Sep 17 00:00:00 2001 From: Abdallah Al-Soqatri Date: Sun, 14 Jul 2024 14:15:00 +0200 Subject: [PATCH 07/17] fixed issue with typing causing build failures. --- CHANGELOG.md | 6 ++++++ packages/core/src/components/Form.tsx | 13 +++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bff3154f9a..2f94a770e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,12 @@ should change the heading of the (upcoming) version to include a major version b --> +# 5.20.0 + +## @rjsf/core + +- Support allowing raising errors from within a custom whatever [#2718](https://github.com/rjsf-team/react-jsonschema-form/issues/2718) + # 5.19.3 ## @rjsf/antd diff --git a/packages/core/src/components/Form.tsx b/packages/core/src/components/Form.tsx index 7c433178d1..bdb80c4f4a 100644 --- a/packages/core/src/components/Form.tsx +++ b/packages/core/src/components/Form.tsx @@ -600,12 +600,13 @@ export default class Form< const fieldNames = this.getFieldNames(pathSchema, formData); const filteredErrors: ErrorSchema = _pick(schemaErrors, fieldNames as unknown as string[]); // Removing undefined and empty errors. - const filterUndefinedErrors = (errors: ErrorSchema): ErrorSchema => { - Object.keys(errors).forEach((key) => { - if (errors[key] === undefined) { - delete errors[key]; - } else if (typeof errors[key] === 'object' && !Array.isArray(errors[key].__errors)) { - filterUndefinedErrors(errors[key]); + const filterUndefinedErrors = (errors: any): ErrorSchema => { + Object.keys(errors).forEach((key: string) => { + const errorKey = key as keyof typeof errors; + if (errors[errorKey] === undefined) { + delete errors[errorKey]; + } else if (typeof errors[errorKey] === 'object' && !Array.isArray(errors[errorKey].__errors)) { + filterUndefinedErrors(errors[errorKey]); } }); return errors; From ea4511efdd0a09593e1f1116508c59232958b6d4 Mon Sep 17 00:00:00 2001 From: Abdallah Al-Soqatri Date: Thu, 25 Jul 2024 19:44:38 +0200 Subject: [PATCH 08/17] Improvement based on feedback --- packages/core/src/components/Form.tsx | 12 +++-- packages/core/test/ArrayField.test.jsx | 69 +++++++++++++++++++++++++ packages/core/test/ObjectField.test.jsx | 53 +++++++++++++++++++ packages/core/test/StringField.test.jsx | 52 +++++++++++++++++++ 4 files changed, 182 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/Form.tsx b/packages/core/src/components/Form.tsx index bdb80c4f4a..f7411a8e82 100644 --- a/packages/core/src/components/Form.tsx +++ b/packages/core/src/components/Form.tsx @@ -585,7 +585,6 @@ export default class Form< omitExtraData = (formData?: T): T | undefined => { const { schema, schemaUtils } = this.state; const retrievedSchema = schemaUtils.retrieveSchema(schema, formData); - const pathSchema = schemaUtils.toPathSchema(retrievedSchema, '', formData); const fieldNames = this.getFieldNames(pathSchema, formData); const newFormData = this.getUsedFormData(formData, fieldNames); @@ -599,14 +598,19 @@ export default class Form< const pathSchema = schemaUtils.toPathSchema(_retrievedSchema, '', formData); const fieldNames = this.getFieldNames(pathSchema, formData); const filteredErrors: ErrorSchema = _pick(schemaErrors, fieldNames as unknown as string[]); + // If the schema is of a primitive type, do not filter out the __errors + if (resolvedSchema?.type !== 'object' && resolvedSchema?.type !== 'array') { + filteredErrors.__errors = schemaErrors.__errors; + } // Removing undefined and empty errors. const filterUndefinedErrors = (errors: any): ErrorSchema => { Object.keys(errors).forEach((key: string) => { const errorKey = key as keyof typeof errors; - if (errors[errorKey] === undefined) { + const errorAtKey = errors[errorKey]; + if (errorAtKey === undefined) { delete errors[errorKey]; - } else if (typeof errors[errorKey] === 'object' && !Array.isArray(errors[errorKey].__errors)) { - filterUndefinedErrors(errors[errorKey]); + } else if (typeof errorAtKey === 'object' && !Array.isArray(errorAtKey.__errors)) { + filterUndefinedErrors(errorAtKey); } }); return errors; diff --git a/packages/core/test/ArrayField.test.jsx b/packages/core/test/ArrayField.test.jsx index 5e36a22ed3..a369aad04d 100644 --- a/packages/core/test/ArrayField.test.jsx +++ b/packages/core/test/ArrayField.test.jsx @@ -5,6 +5,7 @@ import sinon from 'sinon'; import { createFormComponent, createSandbox, submitForm } from './test_utils'; import SchemaField from '../src/components/fields/SchemaField'; +import ArrayField from '../src/components/fields/ArrayField'; const ArrayKeyDataAttr = 'data-rjsf-itemkey'; const ExposedArrayKeyTemplate = function (props) { @@ -157,6 +158,26 @@ const ArrayFieldTestItemTemplate = (props) => { ); }; +const ArrayFieldTest = (props) => { + const onChangeTest = (newFormData, errorSchema, id) => { + if (Array.isArray(newFormData) && newFormData.length === 1) { + const itemValue = newFormData[0]?.text; + if (itemValue !== 'Appie') { + const raiseError = { + ...errorSchema, + 0: { + text: { + __errors: ['Value must be "Appie"'], + }, + }, + }; + props.onChange(newFormData, raiseError, id); + } + } + }; + return ; +}; + describe('ArrayField', () => { let sandbox; const CustomComponent = (props) => { @@ -3196,5 +3217,53 @@ describe('ArrayField', () => { }, }); }); + + it('raise an error and check if the error is displayed', () => { + const { node } = createFormComponent({ + schema, + formData: [ + { + text: 'y', + }, + ], + templates, + fields: { + ArrayField: ArrayFieldTest, + }, + }); + + const inputs = node.querySelectorAll('.field-string input[type=text]'); + act(() => { + fireEvent.change(inputs[0], { target: { value: 'test' } }); + }); + + const errorMessages = node.querySelectorAll('#root_0_text__error'); + expect(errorMessages).to.have.length(1); + const errorMessageContent = node.querySelector('#root_0_text__error .text-danger').textContent; + expect(errorMessageContent).to.contain('Value must be "Appie"'); + }); + + it('should not raise an error if value is correct', () => { + const { node } = createFormComponent({ + schema, + formData: [ + { + text: 'y', + }, + ], + templates, + fields: { + ArrayField: ArrayFieldTest, + }, + }); + + const inputs = node.querySelectorAll('.field-string input[type=text]'); + act(() => { + fireEvent.change(inputs[0], { target: { value: 'Appie' } }); + }); + + const errorMessages = node.querySelectorAll('#root_0_text__error'); + expect(errorMessages).to.have.length(0); + }); }); }); diff --git a/packages/core/test/ObjectField.test.jsx b/packages/core/test/ObjectField.test.jsx index 1f0c2d2cf7..f4e4bfba7b 100644 --- a/packages/core/test/ObjectField.test.jsx +++ b/packages/core/test/ObjectField.test.jsx @@ -5,8 +5,25 @@ import sinon from 'sinon'; import { UI_GLOBAL_OPTIONS_KEY } from '@rjsf/utils'; import SchemaField from '../src/components/fields/SchemaField'; +import ObjectField from '../src/components/fields/ObjectField'; import { createFormComponent, createSandbox, submitForm } from './test_utils'; +const ObjectFieldTest = (props) => { + const onChangeTest = (newFormData, errorSchema, id) => { + const propertyValue = newFormData?.foo; + if (propertyValue !== 'test') { + const raiseError = { + ...errorSchema, + foo: { + __errors: ['Value must be "test"'], + }, + }; + props.onChange(newFormData, raiseError, id); + } + }; + return ; +}; + describe('ObjectField', () => { let sandbox; @@ -208,6 +225,42 @@ describe('ObjectField', () => { expect(node.querySelector(`code#${formContext[key]}`)).to.exist; }); }); + + it('raise an error and check if the error is displayed', () => { + const { node } = createFormComponent({ + schema, + fields: { + ObjectField: ObjectFieldTest, + }, + }); + + const inputs = node.querySelectorAll('.field-string input[type=text]'); + act(() => { + fireEvent.change(inputs[0], { target: { value: 'hello' } }); + }); + + const errorMessages = node.querySelectorAll('#root_foo__error'); + expect(errorMessages).to.have.length(1); + const errorMessageContent = node.querySelector('#root_foo__error .text-danger').textContent; + expect(errorMessageContent).to.contain('Value must be "test"'); + }); + + it('should not raise an error if value is correct', () => { + const { node } = createFormComponent({ + schema, + fields: { + ObjectField: ObjectFieldTest, + }, + }); + + const inputs = node.querySelectorAll('.field-string input[type=text]'); + act(() => { + fireEvent.change(inputs[0], { target: { value: 'test' } }); + }); + + const errorMessages = node.querySelectorAll('#root_foo__error'); + expect(errorMessages).to.have.length(0); + }); }); describe('fields ordering', () => { diff --git a/packages/core/test/StringField.test.jsx b/packages/core/test/StringField.test.jsx index 1bc7d82b3d..08171db583 100644 --- a/packages/core/test/StringField.test.jsx +++ b/packages/core/test/StringField.test.jsx @@ -3,9 +3,25 @@ import { Simulate } from 'react-dom/test-utils'; import { fireEvent, act } from '@testing-library/react'; import sinon from 'sinon'; import { parseDateString, toDateString, TranslatableString, utcToLocal } from '@rjsf/utils'; +import StringField from '../src/components/fields/StringField'; import { createFormComponent, createSandbox, getSelectedOptionValue, submitForm } from './test_utils'; +const StringFieldTest = (props) => { + const onChangeTest = (newFormData, errorSchema, id) => { + const value = newFormData; + let raiseError = errorSchema; + if (value !== 'test') { + raiseError = { + ...raiseError, + __errors: ['Value must be "test"'], + }; + } + props.onChange(newFormData, raiseError, id); + }; + return ; +}; + describe('StringField', () => { let sandbox; @@ -266,6 +282,42 @@ describe('StringField', () => { expect(node.querySelector('input').getAttribute('autocomplete')).eql('family-name'); }); + + it('raise an error and check if the error is displayed', () => { + const { node } = createFormComponent({ + schema: { type: 'string' }, + fields: { + StringField: StringFieldTest, + }, + }); + + const inputs = node.querySelectorAll('.field-string input[type=text]'); + act(() => { + fireEvent.change(inputs[0], { target: { value: 'hello' } }); + }); + + const errorMessages = node.querySelectorAll('#root__error'); + expect(errorMessages).to.have.length(1); + const errorMessageContent = node.querySelector('#root__error .text-danger').textContent; + expect(errorMessageContent).to.contain('Value must be "test"'); + }); + + it('should not raise an error if value is correct', () => { + const { node } = createFormComponent({ + schema: { type: 'string' }, + fields: { + StringField: StringFieldTest, + }, + }); + + const inputs = node.querySelectorAll('.field-string input[type=text]'); + act(() => { + fireEvent.change(inputs[0], { target: { value: 'test' } }); + }); + + const errorMessages = node.querySelectorAll('#root__error'); + expect(errorMessages).to.have.length(0); + }); }); describe('SelectWidget', () => { From b4f5e386000c0f8a7ef965f232e646eff077f49f Mon Sep 17 00:00:00 2001 From: Abdallah Al-Soqatri Date: Sat, 27 Jul 2024 16:26:50 +0200 Subject: [PATCH 09/17] improvement based on feedback and written test for custom widget --- packages/core/src/components/Form.tsx | 7 ++-- packages/core/test/ArrayField.test.jsx | 49 +++++++++++++++++++++++ packages/core/test/ObjectField.test.jsx | 37 ++++++++++++++++++ packages/core/test/StringField.test.jsx | 52 +++++++++++++++++++++++++ 4 files changed, 141 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/Form.tsx b/packages/core/src/components/Form.tsx index f7411a8e82..5017569390 100644 --- a/packages/core/src/components/Form.tsx +++ b/packages/core/src/components/Form.tsx @@ -40,6 +40,7 @@ import _pick from 'lodash/pick'; import _toPath from 'lodash/toPath'; import getDefaultRegistry from '../getDefaultRegistry'; +import { forEach } from 'lodash'; /** The properties that are passed to the `Form` */ export interface FormProps { @@ -598,15 +599,13 @@ export default class Form< const pathSchema = schemaUtils.toPathSchema(_retrievedSchema, '', formData); const fieldNames = this.getFieldNames(pathSchema, formData); const filteredErrors: ErrorSchema = _pick(schemaErrors, fieldNames as unknown as string[]); - // If the schema is of a primitive type, do not filter out the __errors + // If the root schema is of a primitive type, do not filter out the __errors if (resolvedSchema?.type !== 'object' && resolvedSchema?.type !== 'array') { filteredErrors.__errors = schemaErrors.__errors; } // Removing undefined and empty errors. const filterUndefinedErrors = (errors: any): ErrorSchema => { - Object.keys(errors).forEach((key: string) => { - const errorKey = key as keyof typeof errors; - const errorAtKey = errors[errorKey]; + forEach(errors, (errorAtKey, errorKey: keyof typeof errors) => { if (errorAtKey === undefined) { delete errors[errorKey]; } else if (typeof errorAtKey === 'object' && !Array.isArray(errorAtKey.__errors)) { diff --git a/packages/core/test/ArrayField.test.jsx b/packages/core/test/ArrayField.test.jsx index a369aad04d..c80a65ee32 100644 --- a/packages/core/test/ArrayField.test.jsx +++ b/packages/core/test/ArrayField.test.jsx @@ -6,6 +6,7 @@ import sinon from 'sinon'; import { createFormComponent, createSandbox, submitForm } from './test_utils'; import SchemaField from '../src/components/fields/SchemaField'; import ArrayField from '../src/components/fields/ArrayField'; +import { TextWidgetTest } from './StringField.test'; const ArrayKeyDataAttr = 'data-rjsf-itemkey'; const ExposedArrayKeyTemplate = function (props) { @@ -3265,5 +3266,53 @@ describe('ArrayField', () => { const errorMessages = node.querySelectorAll('#root_0_text__error'); expect(errorMessages).to.have.length(0); }); + + it('raise an error and check if the error is displayed using custom text widget', () => { + const { node } = createFormComponent({ + schema, + formData: [ + { + text: 'y', + }, + ], + templates, + widgets: { + TextWidget: TextWidgetTest, + }, + }); + + const inputs = node.querySelectorAll('.field-string input[type=text]'); + act(() => { + fireEvent.change(inputs[0], { target: { value: 'hello' } }); + }); + + const errorMessages = node.querySelectorAll('#root_0_text__error'); + expect(errorMessages).to.have.length(1); + const errorMessageContent = node.querySelector('#root_0_text__error .text-danger').textContent; + expect(errorMessageContent).to.contain('Value must be "test"'); + }); + + it('should not raise an error if value is correct using custom text widget', () => { + const { node } = createFormComponent({ + schema, + formData: [ + { + text: 'y', + }, + ], + templates, + widgets: { + TextWidget: TextWidgetTest, + }, + }); + + const inputs = node.querySelectorAll('.field-string input[type=text]'); + act(() => { + fireEvent.change(inputs[0], { target: { value: 'test' } }); + }); + + const errorMessages = node.querySelectorAll('#root_0_text__error'); + expect(errorMessages).to.have.length(0); + }); }); }); diff --git a/packages/core/test/ObjectField.test.jsx b/packages/core/test/ObjectField.test.jsx index f4e4bfba7b..26e2abb023 100644 --- a/packages/core/test/ObjectField.test.jsx +++ b/packages/core/test/ObjectField.test.jsx @@ -6,6 +6,7 @@ import { UI_GLOBAL_OPTIONS_KEY } from '@rjsf/utils'; import SchemaField from '../src/components/fields/SchemaField'; import ObjectField from '../src/components/fields/ObjectField'; +import { TextWidgetTest } from './StringField.test'; import { createFormComponent, createSandbox, submitForm } from './test_utils'; const ObjectFieldTest = (props) => { @@ -261,6 +262,42 @@ describe('ObjectField', () => { const errorMessages = node.querySelectorAll('#root_foo__error'); expect(errorMessages).to.have.length(0); }); + + it('raise an error and check if the error is displayed using custom text widget', () => { + const { node } = createFormComponent({ + schema, + widgets: { + TextWidget: TextWidgetTest, + }, + }); + + const inputs = node.querySelectorAll('.field-string input[type=text]'); + act(() => { + fireEvent.change(inputs[0], { target: { value: 'hello' } }); + }); + + const errorMessages = node.querySelectorAll('#root_foo__error'); + expect(errorMessages).to.have.length(1); + const errorMessageContent = node.querySelector('#root_foo__error .text-danger').textContent; + expect(errorMessageContent).to.contain('Value must be "test"'); + }); + + it('should not raise an error if value is correct using custom text widget', () => { + const { node } = createFormComponent({ + schema, + fields: { + TextWidget: TextWidgetTest, + }, + }); + + const inputs = node.querySelectorAll('.field-string input[type=text]'); + act(() => { + fireEvent.change(inputs[0], { target: { value: 'test' } }); + }); + + const errorMessages = node.querySelectorAll('#root_foo__error'); + expect(errorMessages).to.have.length(0); + }); }); describe('fields ordering', () => { diff --git a/packages/core/test/StringField.test.jsx b/packages/core/test/StringField.test.jsx index 08171db583..f14d195d67 100644 --- a/packages/core/test/StringField.test.jsx +++ b/packages/core/test/StringField.test.jsx @@ -4,6 +4,7 @@ import { fireEvent, act } from '@testing-library/react'; import sinon from 'sinon'; import { parseDateString, toDateString, TranslatableString, utcToLocal } from '@rjsf/utils'; import StringField from '../src/components/fields/StringField'; +import TextWidget from '../src/components/widgets/TextWidget'; import { createFormComponent, createSandbox, getSelectedOptionValue, submitForm } from './test_utils'; @@ -22,6 +23,21 @@ const StringFieldTest = (props) => { return ; }; +export const TextWidgetTest = (props) => { + const onChangeTest = (newFormData, errorSchema, id) => { + const value = newFormData; + let raiseError = errorSchema; + if (value !== 'test') { + raiseError = { + ...raiseError, + __errors: ['Value must be "test"'], + }; + } + props.onChange(newFormData, raiseError, id); + }; + return ; +}; + describe('StringField', () => { let sandbox; @@ -318,6 +334,42 @@ describe('StringField', () => { const errorMessages = node.querySelectorAll('#root__error'); expect(errorMessages).to.have.length(0); }); + + it('raise an error and check if the error is displayed using custom text widget', () => { + const { node } = createFormComponent({ + schema: { type: 'string' }, + widgets: { + TextWidget: TextWidgetTest, + }, + }); + + const inputs = node.querySelectorAll('.field-string input[type=text]'); + act(() => { + fireEvent.change(inputs[0], { target: { value: 'hello' } }); + }); + + const errorMessages = node.querySelectorAll('#root__error'); + expect(errorMessages).to.have.length(1); + const errorMessageContent = node.querySelector('#root__error .text-danger').textContent; + expect(errorMessageContent).to.contain('Value must be "test"'); + }); + + it('should not raise an error if value is correct using custom text widget', () => { + const { node } = createFormComponent({ + schema: { type: 'string' }, + widgets: { + TextWidget: TextWidgetTest, + }, + }); + + const inputs = node.querySelectorAll('.field-string input[type=text]'); + act(() => { + fireEvent.change(inputs[0], { target: { value: 'test' } }); + }); + + const errorMessages = node.querySelectorAll('#root__error'); + expect(errorMessages).to.have.length(0); + }); }); describe('SelectWidget', () => { From 3f6146ff67abd9b92c245c037243a10aa5d4591e Mon Sep 17 00:00:00 2001 From: Abdallah Al-Soqatri Date: Sat, 27 Jul 2024 17:08:16 +0200 Subject: [PATCH 10/17] documenting the feature --- .../custom-widgets-fields.md | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/packages/docs/docs/advanced-customization/custom-widgets-fields.md b/packages/docs/docs/advanced-customization/custom-widgets-fields.md index 5792887e0c..505ee3b0ac 100644 --- a/packages/docs/docs/advanced-customization/custom-widgets-fields.md +++ b/packages/docs/docs/advanced-customization/custom-widgets-fields.md @@ -92,6 +92,49 @@ The default widgets you can override are: - `UpDownWidget` - `URLWidget` +## Raising errors from within a custom widget or field + +You can raise a custom error by overriding the `onChange` method to raise field/widget errors: + +```tsx +import { RJSFSchema, UiSchema, WidgetProps, RegistryWidgetsType } from '@rjsf/utils'; +import validator from '@rjsf/validator-ajv8'; + +const schema: RJSFSchema = { + type: 'text', + default: 'hello', +}; + +const uiSchema: UiSchema = { + 'ui:widget': 'text', +}; + +const CustomTextWidget = function (props: WidgetProps) { + const raiseErrorOnChange = ({ target: { value } }: ChangeEvent) => { + let raiseError; + if (value !== 'test') { + raiseError = { + __errors: ['Value must be "test"'], + }; + } + props.onChange(value, raiseError); + }; + + return ; +}; + +const widgets: RegistryWidgetsType = { + TextWidget: CustomTextWidget, +}; + +render( +
, + document.getElementById('app') +); +``` + +This creates a custom text widget that raises an error if the input value does not match 'test'. + ## Adding your own custom widgets You can provide your own custom widgets to a uiSchema for the following json data types: From b6ea13df2ac47e146e52f012c1c6eeda3bcbf5da Mon Sep 17 00:00:00 2001 From: Abdallah Al-Soqatri Date: Sat, 27 Jul 2024 21:28:58 +0200 Subject: [PATCH 11/17] docs improvement base on feedback --- packages/core/test/ObjectField.test.jsx | 2 +- .../docs/advanced-customization/custom-widgets-fields.md | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/core/test/ObjectField.test.jsx b/packages/core/test/ObjectField.test.jsx index 26e2abb023..89a83d9b63 100644 --- a/packages/core/test/ObjectField.test.jsx +++ b/packages/core/test/ObjectField.test.jsx @@ -285,7 +285,7 @@ describe('ObjectField', () => { it('should not raise an error if value is correct using custom text widget', () => { const { node } = createFormComponent({ schema, - fields: { + widgets: { TextWidget: TextWidgetTest, }, }); diff --git a/packages/docs/docs/advanced-customization/custom-widgets-fields.md b/packages/docs/docs/advanced-customization/custom-widgets-fields.md index 505ee3b0ac..b5bd57948d 100644 --- a/packages/docs/docs/advanced-customization/custom-widgets-fields.md +++ b/packages/docs/docs/advanced-customization/custom-widgets-fields.md @@ -97,7 +97,7 @@ The default widgets you can override are: You can raise a custom error by overriding the `onChange` method to raise field/widget errors: ```tsx -import { RJSFSchema, UiSchema, WidgetProps, RegistryWidgetsType } from '@rjsf/utils'; +import { ErrorSchema, RJSFSchema, UiSchema, WidgetProps, RegistryWidgetsType } from '@rjsf/utils'; import validator from '@rjsf/validator-ajv8'; const schema: RJSFSchema = { @@ -110,17 +110,18 @@ const uiSchema: UiSchema = { }; const CustomTextWidget = function (props: WidgetProps) { + const { id, value } = props; const raiseErrorOnChange = ({ target: { value } }: ChangeEvent) => { - let raiseError; + let raiseError: ErrorSchema | undefined; if (value !== 'test') { raiseError = { __errors: ['Value must be "test"'], }; } - props.onChange(value, raiseError); + props.onChange(value, raiseError, id); }; - return ; + return ; }; const widgets: RegistryWidgetsType = { From a92d2b54395a02d595fc400a8b8679bd7490d8e5 Mon Sep 17 00:00:00 2001 From: Abdallah Al-Soqatri Date: Sat, 27 Jul 2024 21:33:23 +0200 Subject: [PATCH 12/17] removed empty line --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67ebb9d6dc..fcb057e44a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,6 @@ should change the heading of the (upcoming) version to include a major version b - Support allowing raising errors from within a custom whatever [#2718](https://github.com/rjsf-team/react-jsonschema-form/issues/2718) - # 5.19.4 ## @rjsf/core From 6d22013cd45cf9f05e48bc09abbf0ced2657476a Mon Sep 17 00:00:00 2001 From: Abdallah Al-Soqatri Date: Wed, 31 Jul 2024 09:10:43 +0200 Subject: [PATCH 13/17] fixed lodash import --- packages/core/src/components/Form.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/components/Form.tsx b/packages/core/src/components/Form.tsx index 5017569390..4f0ccb3515 100644 --- a/packages/core/src/components/Form.tsx +++ b/packages/core/src/components/Form.tsx @@ -38,9 +38,9 @@ import _get from 'lodash/get'; import _isEmpty from 'lodash/isEmpty'; import _pick from 'lodash/pick'; import _toPath from 'lodash/toPath'; +import _forEach from 'lodash/forEach'; import getDefaultRegistry from '../getDefaultRegistry'; -import { forEach } from 'lodash'; /** The properties that are passed to the `Form` */ export interface FormProps { @@ -605,7 +605,7 @@ export default class Form< } // Removing undefined and empty errors. const filterUndefinedErrors = (errors: any): ErrorSchema => { - forEach(errors, (errorAtKey, errorKey: keyof typeof errors) => { + _forEach(errors, (errorAtKey, errorKey: keyof typeof errors) => { if (errorAtKey === undefined) { delete errors[errorKey]; } else if (typeof errorAtKey === 'object' && !Array.isArray(errorAtKey.__errors)) { From c527cd1f8680ec79fc7ddf6ca9a7e3e5a5ac9da4 Mon Sep 17 00:00:00 2001 From: Heath C <51679588+heath-freenome@users.noreply.github.com> Date: Wed, 31 Jul 2024 13:10:41 -0700 Subject: [PATCH 14/17] Update packages/core/src/components/Form.tsx Ordered lodash import --- packages/core/src/components/Form.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/components/Form.tsx b/packages/core/src/components/Form.tsx index 4f0ccb3515..3b79135421 100644 --- a/packages/core/src/components/Form.tsx +++ b/packages/core/src/components/Form.tsx @@ -35,10 +35,10 @@ import { Experimental_DefaultFormStateBehavior, } from '@rjsf/utils'; import _get from 'lodash/get'; +import _forEach from 'lodash/forEach'; import _isEmpty from 'lodash/isEmpty'; import _pick from 'lodash/pick'; import _toPath from 'lodash/toPath'; -import _forEach from 'lodash/forEach'; import getDefaultRegistry from '../getDefaultRegistry'; From e151ba56a9dd05936ea82399ecec7a26787b8008 Mon Sep 17 00:00:00 2001 From: Heath C <51679588+heath-freenome@users.noreply.github.com> Date: Wed, 31 Jul 2024 13:11:04 -0700 Subject: [PATCH 15/17] Update packages/core/src/components/Form.tsx --- packages/core/src/components/Form.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/components/Form.tsx b/packages/core/src/components/Form.tsx index 3b79135421..48851cfcf3 100644 --- a/packages/core/src/components/Form.tsx +++ b/packages/core/src/components/Form.tsx @@ -34,8 +34,8 @@ import { ValidatorType, Experimental_DefaultFormStateBehavior, } from '@rjsf/utils'; -import _get from 'lodash/get'; import _forEach from 'lodash/forEach'; +import _get from 'lodash/get'; import _isEmpty from 'lodash/isEmpty'; import _pick from 'lodash/pick'; import _toPath from 'lodash/toPath'; From 2161fa1bd0f13d12a19fa8bdce0f3458808ebd30 Mon Sep 17 00:00:00 2001 From: Heath C <51679588+heath-freenome@users.noreply.github.com> Date: Fri, 2 Aug 2024 12:20:39 -0700 Subject: [PATCH 16/17] Update CHANGELOG.md Added missing packages --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcb057e44a..945f674b50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,8 +20,15 @@ should change the heading of the (upcoming) version to include a major version b ## @rjsf/core -- Support allowing raising errors from within a custom whatever [#2718](https://github.com/rjsf-team/react-jsonschema-form/issues/2718) +- Support allowing raising errors from within a custom Widget [#2718](https://github.com/rjsf-team/react-jsonschema-form/issues/2718) +## @rjsf/utils + +- Updated the `WidgetProps` type to add `es?: ErrorSchema, id?: string` to the params of the `onChange` handler function + +## Dev / docs / playground + +- Update the `custom-widget-fields.md` to add documentation for how to raise errors from a custom widget or field # 5.19.4 ## @rjsf/core From 6f8513b6c2598962b615e7a5bfbe8938e2df7836 Mon Sep 17 00:00:00 2001 From: Heath C <51679588+heath-freenome@users.noreply.github.com> Date: Fri, 2 Aug 2024 12:21:00 -0700 Subject: [PATCH 17/17] Update CHANGELOG.md Added missing space --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 945f674b50..ad4c1c3f6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ should change the heading of the (upcoming) version to include a major version b ## Dev / docs / playground - Update the `custom-widget-fields.md` to add documentation for how to raise errors from a custom widget or field + # 5.19.4 ## @rjsf/core