From 119bfc94b2d6db54f2bd4446408b9f62c2215767 Mon Sep 17 00:00:00 2001 From: Piotr-Debicki <105816286+Piotr-Debicki@users.noreply.github.com> Date: Tue, 4 Feb 2025 16:49:59 -0500 Subject: [PATCH 01/15] fix additional property defaults not generating --- packages/core/src/components/Form.tsx | 13 ++++++++++++- packages/utils/src/createSchemaUtils.ts | 3 +++ packages/utils/src/schema/getDefaultFormState.ts | 15 ++++++++++++++- packages/validator-ajv6/src/validator.ts | 2 +- .../src/processRawValidationErrors.ts | 2 +- 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/Form.tsx b/packages/core/src/components/Form.tsx index 5a5d7a800c..a02df1a11c 100644 --- a/packages/core/src/components/Form.tsx +++ b/packages/core/src/components/Form.tsx @@ -252,6 +252,8 @@ export interface FormState( @@ -118,6 +120,7 @@ class SchemaUtils * The formData should take precedence unless it's not valid. This is useful when for example the value from formData does not exist in the schema 'enum' property, in such cases we take the value from the defaults because the value from the formData is not valid. */ shouldMergeDefaultsIntoFormData?: boolean; + /** Optional flag, if true, indicates that defaults have already been generated */ + initDefaultsGenerated?: boolean; } /** Computes the defaults for the current `schema` given the `rawFormData` and `parentDefaults` if any. This drills into @@ -203,6 +205,7 @@ export function computeDefaults = {}, defaults?: T | T[] | undefined ): T { @@ -452,6 +457,7 @@ export function getObjectDefaults( acc, @@ -467,7 +473,7 @@ export function getObjectDefaults( @@ -532,6 +539,7 @@ export function getArrayDefaults = {}, defaults?: T | T[] | undefined ): T | T[] | undefined { @@ -560,6 +568,7 @@ export function getArrayDefaults @@ -706,6 +718,7 @@ export default function getDefaultFormState< experimental_customMergeAllOf, rawFormData: formData, shouldMergeDefaultsIntoFormData: true, + initDefaultsGenerated, }); // If the formData is an object or an array, add additional properties from formData and override formData with diff --git a/packages/validator-ajv6/src/validator.ts b/packages/validator-ajv6/src/validator.ts index 79c221a246..5b8f0bcd6b 100644 --- a/packages/validator-ajv6/src/validator.ts +++ b/packages/validator-ajv6/src/validator.ts @@ -152,7 +152,7 @@ export default class AJV6Validator(this, schema, formData, rootSchema, true) as T; + const newFormData = getDefaultFormState(this, schema, formData, rootSchema, false, true) as T; const errorHandler = customValidate(newFormData, createErrorHandler(newFormData), uiSchema); const userErrorSchema = unwrapErrorHandler(errorHandler); diff --git a/packages/validator-ajv8/src/processRawValidationErrors.ts b/packages/validator-ajv8/src/processRawValidationErrors.ts index 52dfc29913..0c0d22b591 100644 --- a/packages/validator-ajv8/src/processRawValidationErrors.ts +++ b/packages/validator-ajv8/src/processRawValidationErrors.ts @@ -150,7 +150,7 @@ export default function processRawValidationErrors< } // Include form data with undefined values, which is required for custom validation. - const newFormData = getDefaultFormState(validator, schema, formData, schema, true) as T; + const newFormData = getDefaultFormState(validator, schema, formData, schema, false, true) as T; const errorHandler = customValidate(newFormData, createErrorHandler(newFormData), uiSchema); const userErrorSchema = unwrapErrorHandler(errorHandler); From a9497a620b0dd650edd99b68531719029c22ad20 Mon Sep 17 00:00:00 2001 From: Piotr-Debicki <105816286+Piotr-Debicki@users.noreply.github.com> Date: Wed, 5 Feb 2025 14:14:06 -0500 Subject: [PATCH 02/15] update tests --- .../components/fields/MultiSchemaField.tsx | 2 +- packages/utils/src/types.ts | 1 + .../test/schema/getDefaultFormStateTest.ts | 53 +++++++++++-------- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/packages/core/src/components/fields/MultiSchemaField.tsx b/packages/core/src/components/fields/MultiSchemaField.tsx index 498713038c..db4b38649e 100644 --- a/packages/core/src/components/fields/MultiSchemaField.tsx +++ b/packages/core/src/components/fields/MultiSchemaField.tsx @@ -126,7 +126,7 @@ class AnyOfField { diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index 2d6c8335c3..5ba6d1ded8 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -1082,6 +1082,7 @@ export interface SchemaUtilsType { - expect(getDefaultFormState(testValidator, schema, undefined, schema, includeUndefinedValues)).toEqual( + expect(getDefaultFormState(testValidator, schema, undefined, schema, false, includeUndefinedValues)).toEqual( expected ); }); @@ -458,7 +459,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType }; test('getDefaultFormState', () => { - expect(getDefaultFormState(testValidator, schema, undefined, schema, includeUndefinedValues)).toEqual( + expect(getDefaultFormState(testValidator, schema, undefined, schema, false, includeUndefinedValues)).toEqual( expected ); }); @@ -794,7 +795,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType test('getDefaultFormState', () => { expect( - getDefaultFormState(testValidator, schema, rawFormData, schema, false, { + getDefaultFormState(testValidator, schema, rawFormData, schema, false, false, { emptyObjectFields: 'populateAllDefaults', allOf: 'skipDefaults', arrayMinItems: { @@ -873,9 +874,9 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType const expected = {}; test('getDefaultFormState', () => { - expect(getDefaultFormState(testValidator, schema, undefined, schema, includeUndefinedValues)).toEqual( - expected - ); + expect( + getDefaultFormState(testValidator, schema, undefined, schema, false, includeUndefinedValues) + ).toEqual(expected); }); test('computeDefaults', () => { @@ -1050,6 +1051,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType rawFormData, schema, includeUndefinedValues, + false, experimental_defaultFormStateBehavior ) ).toEqual({ @@ -1131,6 +1133,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType rawFormData, schema, includeUndefinedValues, + false, experimental_defaultFormStateBehavior ) ).toEqual({ @@ -1298,6 +1301,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType rawFormData, schema, undefined, + false, experimental_defaultFormStateBehavior ) ).toEqual(expected); @@ -1380,6 +1384,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType undefined, schema, undefined, + false, experimental_defaultFormStateBehavior ) ).toEqual(expected); @@ -1427,6 +1432,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType undefined, schema, undefined, + false, experimental_defaultFormStateBehavior ) ).toEqual(expected); @@ -1474,6 +1480,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType undefined, schema, undefined, + false, experimental_defaultFormStateBehavior ) ).toEqual(expected); @@ -1536,6 +1543,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType rawFormData, schema, undefined, + false, experimental_defaultFormStateBehavior ) ).toEqual(expected); @@ -1588,7 +1596,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType const expected = ['Raphael', 'Michaelangelo', 'Unknown', 'Unknown']; test('getDefaultFormState', () => { - expect(getDefaultFormState(testValidator, schema, undefined, schema, includeUndefinedValues)).toEqual( + expect(getDefaultFormState(testValidator, schema, undefined, schema, false, includeUndefinedValues)).toEqual( expected ); }); @@ -1647,6 +1655,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType schema, undefined, schema, + false, includeUndefinedValues, experimental_defaultFormStateBehavior ) @@ -1710,7 +1719,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType const expected: undefined[] = [undefined, undefined, undefined, undefined]; test('getDefaultFormState', () => { - expect(getDefaultFormState(testValidator, schema, undefined, schema, includeUndefinedValues)).toEqual( + expect(getDefaultFormState(testValidator, schema, undefined, schema, false, includeUndefinedValues)).toEqual( expected ); }); @@ -1757,7 +1766,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType const expected = ['ConstFromRoot', 'ConstFromRoot', 'Constant', 'Constant']; test('getDefaultFormState', () => { - expect(getDefaultFormState(testValidator, schema, undefined, schema, includeUndefinedValues)).toEqual( + expect(getDefaultFormState(testValidator, schema, undefined, schema, false, includeUndefinedValues)).toEqual( expected ); }); @@ -1809,7 +1818,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType const expected: never[] = []; test('getDefaultFormState', () => { - expect(getDefaultFormState(testValidator, schema, undefined, schema, includeUndefinedValues)).toEqual( + expect(getDefaultFormState(testValidator, schema, undefined, schema, false, includeUndefinedValues)).toEqual( expected ); }); @@ -3855,7 +3864,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType }, }; expect( - getDefaultFormState(testValidator, schema, {}, undefined, undefined, { + getDefaultFormState(testValidator, schema, {}, undefined, undefined, false, { emptyObjectFields: 'populateRequiredDefaults', }) ).toEqual({ name: {} }); @@ -3886,7 +3895,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType }, }; expect( - getDefaultFormState(testValidator, schema, {}, undefined, undefined, { + getDefaultFormState(testValidator, schema, {}, undefined, undefined, false, { emptyObjectFields: 'populateRequiredDefaults', }) ).toEqual({ @@ -3920,7 +3929,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType ], }; expect( - getDefaultFormState(testValidator, schema, {}, undefined, undefined, { + getDefaultFormState(testValidator, schema, {}, undefined, undefined, false, { emptyObjectFields: 'populateRequiredDefaults', }) ).toEqual({ foo: 'fooVal', baz: 'bazIsRequired' }); @@ -4052,7 +4061,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType }, }; expect( - getDefaultFormState(testValidator, schema, {}, undefined, undefined, { + getDefaultFormState(testValidator, schema, {}, undefined, undefined, false, { emptyObjectFields: 'populateRequiredDefaults', }) ).toEqual({ name: {} }); @@ -4083,7 +4092,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType }, }; expect( - getDefaultFormState(testValidator, schema, {}, undefined, undefined, { + getDefaultFormState(testValidator, schema, {}, undefined, undefined, false, { emptyObjectFields: 'populateRequiredDefaults', }) ).toEqual({ @@ -4117,7 +4126,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType ], }; expect( - getDefaultFormState(testValidator, schema, {}, undefined, undefined, { + getDefaultFormState(testValidator, schema, {}, undefined, undefined, false, { emptyObjectFields: 'populateRequiredDefaults', }) ).toEqual({ foo: 'fooVal', baz: 'bazIsRequired' }); @@ -4635,7 +4644,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType it('returns field value of default when formData has undefined for field and `useDefaultIfFormDataUndefined`', () => { const formData = { field: undefined }; expect( - getDefaultFormState(testValidator, schema, formData, undefined, undefined, { + getDefaultFormState(testValidator, schema, formData, undefined, undefined, false, { mergeDefaultsIntoFormData: 'useDefaultIfFormDataUndefined', }) ).toEqual(defaultedFormData); @@ -4683,7 +4692,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType }, }; expect( - getDefaultFormState(testValidator, schema, { requiredArray: ['raw0'] }, schema, false, { + getDefaultFormState(testValidator, schema, { requiredArray: ['raw0'] }, schema, false, false, { arrayMinItems: { mergeExtraDefaults: true }, }) ).toEqual({ @@ -4703,7 +4712,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType required: ['requiredArray'], }; expect( - getDefaultFormState(testValidator, schema, undefined, schema, false, { + getDefaultFormState(testValidator, schema, undefined, schema, false, false, { arrayMinItems: { populate: 'requiredOnly' }, }) ).toEqual({ requiredArray: ['default0', 'default0'] }); @@ -4721,7 +4730,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType required: ['requiredArray'], }; expect( - getDefaultFormState(testValidator, schema, { requiredArray: ['raw0'] }, schema, false, { + getDefaultFormState(testValidator, schema, { requiredArray: ['raw0'] }, schema, false, false, { arrayMinItems: { populate: 'requiredOnly' }, }) ).toEqual({ requiredArray: ['raw0'] }); @@ -4739,7 +4748,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType required: ['requiredArray'], }; expect( - getDefaultFormState(testValidator, schema, { requiredArray: ['raw0'] }, schema, false, { + getDefaultFormState(testValidator, schema, { requiredArray: ['raw0'] }, schema, false, false, { arrayMinItems: { populate: 'requiredOnly', mergeExtraDefaults: true }, }) ).toEqual({ requiredArray: ['raw0', 'default0'] }); @@ -4762,7 +4771,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType required: ['stringArray', 'numberArray'], }; expect( - getDefaultFormState(testValidator, schema, {}, schema, false, { + getDefaultFormState(testValidator, schema, {}, schema, false, false, { arrayMinItems: { computeSkipPopulate: (_, schema) => !Array.isArray(schema?.items) && typeof schema?.items !== 'boolean' && schema?.items?.type === 'number', From 395d06651eed13d24fd498fee0cc130c2b460579 Mon Sep 17 00:00:00 2001 From: Piotr-Debicki <105816286+Piotr-Debicki@users.noreply.github.com> Date: Wed, 5 Feb 2025 14:38:37 -0500 Subject: [PATCH 03/15] update descriptions, rm log --- packages/core/src/components/Form.tsx | 7 ------- packages/utils/src/createSchemaUtils.ts | 2 +- packages/utils/src/schema/getDefaultFormState.ts | 3 ++- packages/utils/src/types.ts | 1 + 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/core/src/components/Form.tsx b/packages/core/src/components/Form.tsx index a02df1a11c..07f18fb2f1 100644 --- a/packages/core/src/components/Form.tsx +++ b/packages/core/src/components/Form.tsx @@ -426,13 +426,6 @@ export default class Form< ); } - if (schema.title === 'console') { - console.log('schema', schema); - console.log('retrievedSchema', retrievedSchema); - console.log('edit', edit); - console.log('props', props); - } - const formData: T = schemaUtils.getDefaultFormState(schema, inputFormData, state.initialDefaultsGenerated) as T; const _retrievedSchema = this.updateRetrievedSchema( retrievedSchema ?? schemaUtils.retrieveSchema(schema, formData) diff --git a/packages/utils/src/createSchemaUtils.ts b/packages/utils/src/createSchemaUtils.ts index e5c91c3043..709bfbe418 100644 --- a/packages/utils/src/createSchemaUtils.ts +++ b/packages/utils/src/createSchemaUtils.ts @@ -103,7 +103,7 @@ class SchemaUtils * The formData should take precedence unless it's not valid. This is useful when for example the value from formData does not exist in the schema 'enum' property, in such cases we take the value from the defaults because the value from the formData is not valid. */ shouldMergeDefaultsIntoFormData?: boolean; - /** Optional flag, if true, indicates that defaults have already been generated */ + /** Indicates whether or not initial defaults have been generated */ initDefaultsGenerated?: boolean; } @@ -682,6 +682,7 @@ export function getDefaultBasedOnSchemaType< * @param theSchema - The schema for which the default state is desired * @param [formData] - The current formData, if any, onto which to provide any missing defaults * @param [rootSchema] - The root schema, used to primarily to look up `$ref`s + * @param initDefaultsGenerated - indicates whether or not initial defaults have been generated * @param [includeUndefinedValues=false] - Optional flag, if true, cause undefined values to be added as defaults. * If "excludeObjectChildren", cause undefined values for this object and pass `includeUndefinedValues` as * false when computing defaults for any nested object properties. diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index 5ba6d1ded8..164cfe034d 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -1074,6 +1074,7 @@ export interface SchemaUtilsType Date: Wed, 5 Feb 2025 14:50:53 -0500 Subject: [PATCH 04/15] fix core form tests --- packages/core/test/Form.test.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/test/Form.test.jsx b/packages/core/test/Form.test.jsx index b774049fd9..d3872d3d94 100644 --- a/packages/core/test/Form.test.jsx +++ b/packages/core/test/Form.test.jsx @@ -147,6 +147,7 @@ describeRepeated('Form common', (createFormComponent) => { schemaValidationErrorSchema: undefined, schemaUtils: sinon.match.object, retrievedSchema: schema, + initialDefaultsGenerated: true, }); }); }); @@ -1512,6 +1513,7 @@ describeRepeated('Form common', (createFormComponent) => { schemaValidationErrorSchema: undefined, schemaUtils: sinon.match.object, retrievedSchema: formProps.schema, + initialDefaultsGenerated: true, }); }); }); From adb0597cc36146e39be06c8cb33bd6e4d6435963 Mon Sep 17 00:00:00 2001 From: Piotr-Debicki <105816286+Piotr-Debicki@users.noreply.github.com> Date: Wed, 5 Feb 2025 17:30:00 -0500 Subject: [PATCH 05/15] add object additionalProperties defaults test --- packages/core/test/ObjectField.test.jsx | 159 ++++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/packages/core/test/ObjectField.test.jsx b/packages/core/test/ObjectField.test.jsx index 5bc7ec8a5d..2ce3d18dff 100644 --- a/packages/core/test/ObjectField.test.jsx +++ b/packages/core/test/ObjectField.test.jsx @@ -1142,6 +1142,165 @@ describe('ObjectField', () => { }); }); + it('should generate the specifed default key and value inputs if default is provided outside of additionalProperties schema', () => { + const customSchema = { + ...schema, + default: { + defaultKey: 'defaultValue', + }, + }; + const { onChange } = createFormComponent({ + schema: customSchema, + formData: {}, + }); + + sinon.assert.calledWithMatch(onChange.lastCall, { + formData: { + defaultKey: 'defaultValue', + }, + }); + }); + + it('should generate the specifed default key/value input with custom formData provided', () => { + const customSchema = { + ...schema, + default: { + defaultKey: 'defaultValue', + }, + }; + const { onChange } = createFormComponent({ + schema: customSchema, + formData: { + someData: 'someValue', + }, + }); + + sinon.assert.calledWithMatch(onChange.lastCall, { + formData: { + defaultKey: 'defaultValue', + someData: 'someValue', + }, + }); + }); + + it('should edit the specifed default key without duplicating', () => { + const customSchema = { + ...schema, + default: { + defaultKey: 'defaultValue', + }, + }; + const { node, onChange } = createFormComponent({ + schema: customSchema, + formData: {}, + }); + + fireEvent.blur(node.querySelector('#root_defaultKey-key'), { target: { value: 'newDefaultKey' } }); + + sinon.assert.calledWithMatch(onChange.lastCall, { + formData: { + newDefaultKey: 'defaultValue', + }, + }); + }); + + it('should remove the specifed default key/value input item', () => { + const customSchema = { + ...schema, + default: { + defaultKey: 'defaultValue', + }, + }; + const { node, onChange } = createFormComponent({ + schema: customSchema, + formData: {}, + }); + + fireEvent.click(node.querySelector('.array-item-remove')); + + sinon.assert.calledWithMatch(onChange.lastCall, { + formData: {}, + }); + }); + + it('should handle nested additional property default key/value input generation', () => { + const customSchema = { + ...schema, + default: { + defaultKey: 'defaultValue', + }, + properties: { + nested: { + type: 'object', + properties: { + bar: { + type: 'object', + additionalProperties: { + type: 'string', + }, + default: { + baz: 'value', + }, + }, + }, + }, + }, + }; + + const { onChange } = createFormComponent({ + schema: customSchema, + formData: {}, + }); + + sinon.assert.calledWithMatch(onChange.lastCall, { + formData: { + defaultKey: 'defaultValue', + nested: { + bar: { + baz: 'value', + }, + }, + }, + }); + }); + + it('should remove nested additional property default key/value input', () => { + const customSchema = { + ...schema, + properties: { + nested: { + type: 'object', + properties: { + bar: { + type: 'object', + additionalProperties: { + type: 'string', + }, + default: { + baz: 'value', + }, + }, + }, + }, + }, + }; + + const { node, onChange } = createFormComponent({ + schema: customSchema, + formData: {}, + }); + + fireEvent.click(node.querySelector('.array-item-remove')); + + sinon.assert.calledWithMatch(onChange.lastCall, { + formData: { + nested: { + bar: {}, + }, + }, + }); + }); + it('should not provide an expand button if length equals maxProperties', () => { const { node } = createFormComponent({ schema: { maxProperties: 1, ...schema }, From 06255abbf0e439c6656e3d3fa58e0f4aa205c3f9 Mon Sep 17 00:00:00 2001 From: Piotr-Debicki <105816286+Piotr-Debicki@users.noreply.github.com> Date: Wed, 5 Feb 2025 19:05:07 -0500 Subject: [PATCH 06/15] fix verbiage --- packages/core/test/ObjectField.test.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/test/ObjectField.test.jsx b/packages/core/test/ObjectField.test.jsx index 2ce3d18dff..b20a63aeed 100644 --- a/packages/core/test/ObjectField.test.jsx +++ b/packages/core/test/ObjectField.test.jsx @@ -1142,7 +1142,7 @@ describe('ObjectField', () => { }); }); - it('should generate the specifed default key and value inputs if default is provided outside of additionalProperties schema', () => { + it('should generate the specified default key and value inputs if default is provided outside of additionalProperties schema', () => { const customSchema = { ...schema, default: { @@ -1161,7 +1161,7 @@ describe('ObjectField', () => { }); }); - it('should generate the specifed default key/value input with custom formData provided', () => { + it('should generate the specified default key/value input with custom formData provided', () => { const customSchema = { ...schema, default: { @@ -1183,7 +1183,7 @@ describe('ObjectField', () => { }); }); - it('should edit the specifed default key without duplicating', () => { + it('should edit the specified default key without duplicating', () => { const customSchema = { ...schema, default: { @@ -1204,7 +1204,7 @@ describe('ObjectField', () => { }); }); - it('should remove the specifed default key/value input item', () => { + it('should remove the specified default key/value input item', () => { const customSchema = { ...schema, default: { From 6b1354a1caeb8a325701a4eb3b2b36615e59d8a3 Mon Sep 17 00:00:00 2001 From: Piotr-Debicki <105816286+Piotr-Debicki@users.noreply.github.com> Date: Fri, 7 Feb 2025 13:33:09 -0500 Subject: [PATCH 07/15] allow regeneration of additional property defaults on reset --- packages/core/src/components/Form.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/components/Form.tsx b/packages/core/src/components/Form.tsx index 07f18fb2f1..f42130ed16 100644 --- a/packages/core/src/components/Form.tsx +++ b/packages/core/src/components/Form.tsx @@ -761,6 +761,7 @@ export default class Form< errors: [] as unknown, schemaValidationErrors: [] as unknown, schemaValidationErrorSchema: {}, + initialDefaultsGenerated: false, } as FormState; this.setState(state, () => onChange && onChange({ ...this.state, ...state })); From a0e64aa4f4646c8f3a15a3569a92e82c97fe532d Mon Sep 17 00:00:00 2001 From: Piotr-Debicki <105816286+Piotr-Debicki@users.noreply.github.com> Date: Fri, 7 Feb 2025 15:56:27 -0500 Subject: [PATCH 08/15] update verbiage & docs --- packages/core/src/components/Form.tsx | 2 +- .../docs/api-reference/utility-functions.md | 1 + packages/utils/src/createSchemaUtils.ts | 6 ++-- .../utils/src/schema/getDefaultFormState.ts | 30 +++++++++---------- packages/utils/src/types.ts | 4 +-- 5 files changed, 22 insertions(+), 21 deletions(-) diff --git a/packages/core/src/components/Form.tsx b/packages/core/src/components/Form.tsx index f42130ed16..37979dfd57 100644 --- a/packages/core/src/components/Form.tsx +++ b/packages/core/src/components/Form.tsx @@ -252,7 +252,7 @@ export interface FormState( @@ -120,7 +120,7 @@ class SchemaUtils * The formData should take precedence unless it's not valid. This is useful when for example the value from formData does not exist in the schema 'enum' property, in such cases we take the value from the defaults because the value from the formData is not valid. */ shouldMergeDefaultsIntoFormData?: boolean; - /** Indicates whether or not initial defaults have been generated */ - initDefaultsGenerated?: boolean; + /** Indicates whether initial defaults have been generated */ + initialDefaultsGenerated?: boolean; } /** Computes the defaults for the current `schema` given the `rawFormData` and `parentDefaults` if any. This drills into @@ -205,7 +205,7 @@ export function computeDefaults = {}, defaults?: T | T[] | undefined ): T { @@ -457,7 +457,7 @@ export function getObjectDefaults( acc, @@ -473,7 +473,7 @@ export function getObjectDefaults( @@ -539,7 +539,7 @@ export function getArrayDefaults = {}, defaults?: T | T[] | undefined ): T | T[] | undefined { @@ -568,7 +568,7 @@ export function getArrayDefaults @@ -719,7 +719,7 @@ export default function getDefaultFormState< experimental_customMergeAllOf, rawFormData: formData, shouldMergeDefaultsIntoFormData: true, - initDefaultsGenerated, + initialDefaultsGenerated, }); // If the formData is an object or an array, add additional properties from formData and override formData with diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index 164cfe034d..7e73b9514a 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -1074,7 +1074,7 @@ export interface SchemaUtilsType Date: Fri, 7 Feb 2025 16:23:39 -0500 Subject: [PATCH 09/15] update CHANGELOG.md --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b819d00e67..7f5cccb637 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,25 @@ it according to semantic versioning. For example, if your PR adds a breaking cha should change the heading of the (upcoming) version to include a major version bump. --> +# 5.24.4 + +## @rjsf/core + +- Added `initialDefaultsGenerated` flag to state, which indicates whether the initial generation of defaults has been completed +- Added `ObjectField` tests for additionalProperties with defaults + +## @rjsf/utils + +- Updated `getDefaultFormState` to add a new `initialDefaultsGenerated` prop flag, along with type definitions, fixing uneditable & permanent defaults with additional properties [3759](https://github.com/rjsf-team/react-jsonschema-form/issues/3759) +- Updated `createSchemaUtils` definition to reflect addition of `initialDefaultsGenerated` +- Updated existing tests where `getDefaultFormState` is used to reflect addition of `initialDefaultsGenerated` + +## @rjsf/docs +- Updated docs for `getDefaultFormState` to reflect addition of `initialDefaultsGenerated` prop + +## @rjsf/validator-ajv6 @rjsf/validator-avv8 +- Updated `getDefaultFormState` calls to reflect addition of `initialDefaultsGenerated` + # 5.24.3 ## @rjsf/utils From 8c400bc7af035f6dd50407ec4acc019d1845e905 Mon Sep 17 00:00:00 2001 From: Piotr-Debicki <105816286+Piotr-Debicki@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:03:22 -0500 Subject: [PATCH 10/15] Update CHANGELOG.md Co-authored-by: Heath C <51679588+heath-freenome@users.noreply.github.com> --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f5cccb637..624a916211 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,12 @@ should change the heading of the (upcoming) version to include a major version b ## @rjsf/docs - Updated docs for `getDefaultFormState` to reflect addition of `initialDefaultsGenerated` prop -## @rjsf/validator-ajv6 @rjsf/validator-avv8 +## @rjsf/validator-ajv6 + +- Updated `getDefaultFormState` calls to reflect addition of `initialDefaultsGenerated` + +## @rjsf/validator-ajv8 + - Updated `getDefaultFormState` calls to reflect addition of `initialDefaultsGenerated` # 5.24.3 From c75a889aa9192ae40c9bae662ff46c9d28b75ef6 Mon Sep 17 00:00:00 2001 From: Piotr-Debicki <105816286+Piotr-Debicki@users.noreply.github.com> Date: Mon, 29 Sep 2025 22:12:52 -0400 Subject: [PATCH 11/15] move new param & update tests --- packages/core/src/components/Form.tsx | 7 +- .../components/fields/MultiSchemaField.tsx | 2 +- packages/core/test/ObjectField.test.jsx | 4 +- packages/utils/src/createSchemaUtils.ts | 6 +- .../utils/src/schema/getDefaultFormState.ts | 4 +- packages/utils/src/types.ts | 4 +- .../test/schema/getDefaultFormStateTest.ts | 95 +++++++++---------- .../src/processRawValidationErrors.ts | 2 +- 8 files changed, 60 insertions(+), 64 deletions(-) diff --git a/packages/core/src/components/Form.tsx b/packages/core/src/components/Form.tsx index f5099b1e20..cf137f421f 100644 --- a/packages/core/src/components/Form.tsx +++ b/packages/core/src/components/Form.tsx @@ -464,7 +464,12 @@ export default class Form< } const rootSchema = schemaUtils.getRootSchema(); - const formData: T = schemaUtils.getDefaultFormState(rootSchema, inputFormData, state.initialDefaultsGenerated) as T; + const formData: T = schemaUtils.getDefaultFormState( + rootSchema, + inputFormData, + false, + state.initialDefaultsGenerated, + ) as T; const _retrievedSchema = this.updateRetrievedSchema( retrievedSchema ?? schemaUtils.retrieveSchema(rootSchema, formData), ); diff --git a/packages/core/src/components/fields/MultiSchemaField.tsx b/packages/core/src/components/fields/MultiSchemaField.tsx index 2286a35c55..819fe65d1c 100644 --- a/packages/core/src/components/fields/MultiSchemaField.tsx +++ b/packages/core/src/components/fields/MultiSchemaField.tsx @@ -127,7 +127,7 @@ class AnyOfField { diff --git a/packages/core/test/ObjectField.test.jsx b/packages/core/test/ObjectField.test.jsx index fbbf6c5f82..704a52cedc 100644 --- a/packages/core/test/ObjectField.test.jsx +++ b/packages/core/test/ObjectField.test.jsx @@ -1218,7 +1218,7 @@ describe('ObjectField', () => { formData: {}, }); - fireEvent.click(node.querySelector('.array-item-remove')); + fireEvent.click(node.querySelector('.rjsf-object-property-remove')); sinon.assert.calledWithMatch(onChange.lastCall, { formData: {}, @@ -1292,7 +1292,7 @@ describe('ObjectField', () => { formData: {}, }); - fireEvent.click(node.querySelector('.array-item-remove')); + fireEvent.click(node.querySelector('.rjsf-object-property-remove')); sinon.assert.calledWithMatch(onChange.lastCall, { formData: { diff --git a/packages/utils/src/createSchemaUtils.ts b/packages/utils/src/createSchemaUtils.ts index 570d33b248..b9c9693875 100644 --- a/packages/utils/src/createSchemaUtils.ts +++ b/packages/utils/src/createSchemaUtils.ts @@ -164,27 +164,27 @@ class SchemaUtils( this.validator, schema, formData, this.rootSchema, - initialDefaultsGenerated, includeUndefinedValues, this.experimental_defaultFormStateBehavior, this.experimental_customMergeAllOf, + initialDefaultsGenerated, ); } diff --git a/packages/utils/src/schema/getDefaultFormState.ts b/packages/utils/src/schema/getDefaultFormState.ts index 939896a35e..8325272e5b 100644 --- a/packages/utils/src/schema/getDefaultFormState.ts +++ b/packages/utils/src/schema/getDefaultFormState.ts @@ -732,12 +732,12 @@ export function getDefaultBasedOnSchemaType< * @param theSchema - The schema for which the default state is desired * @param [formData] - The current formData, if any, onto which to provide any missing defaults * @param [rootSchema] - The root schema, used to primarily to look up `$ref`s - * @param initialDefaultsGenerated - Indicates whether or not initial defaults have been generated * @param [includeUndefinedValues=false] - Optional flag, if true, cause undefined values to be added as defaults. * If "excludeObjectChildren", cause undefined values for this object and pass `includeUndefinedValues` as * false when computing defaults for any nested object properties. * @param [experimental_defaultFormStateBehavior] Optional configuration object, if provided, allows users to override default form state behavior * @param [experimental_customMergeAllOf] - Optional function that allows for custom merging of `allOf` schemas + * @param initialDefaultsGenerated - Indicates whether or not initial defaults have been generated * @returns - The resulting `formData` with all the defaults provided */ export default function getDefaultFormState< @@ -749,10 +749,10 @@ export default function getDefaultFormState< theSchema: S, formData?: T, rootSchema?: S, - initialDefaultsGenerated?: boolean, includeUndefinedValues: boolean | 'excludeObjectChildren' = false, experimental_defaultFormStateBehavior?: Experimental_DefaultFormStateBehavior, experimental_customMergeAllOf?: Experimental_CustomMergeAllOf, + initialDefaultsGenerated?: boolean, ) { if (!isObject(theSchema)) { throw new Error('Invalid schema: ' + theSchema); diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts index 563632b5bb..4df2253481 100644 --- a/packages/utils/src/types.ts +++ b/packages/utils/src/types.ts @@ -1183,17 +1183,17 @@ export interface SchemaUtilsType { - expect(getDefaultFormState(testValidator, schema, undefined, schema, false, includeUndefinedValues)).toEqual( - expected + expect(getDefaultFormState(testValidator, schema, undefined, schema, includeUndefinedValues)).toEqual( + expected, ); }); @@ -460,8 +459,8 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType }; test('getDefaultFormState', () => { - expect(getDefaultFormState(testValidator, schema, undefined, schema, false, includeUndefinedValues)).toEqual( - expected + expect(getDefaultFormState(testValidator, schema, undefined, schema, includeUndefinedValues)).toEqual( + expected, ); }); @@ -796,7 +795,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType test('getDefaultFormState', () => { expect( - getDefaultFormState(testValidator, schema, rawFormData, schema, false, false, { + getDefaultFormState(testValidator, schema, rawFormData, schema, false, { emptyObjectFields: 'populateAllDefaults', allOf: 'skipDefaults', arrayMinItems: { @@ -875,9 +874,9 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType const expected = {}; test('getDefaultFormState', () => { - expect( - getDefaultFormState(testValidator, schema, undefined, schema, false, includeUndefinedValues) - ).toEqual(expected); + expect(getDefaultFormState(testValidator, schema, undefined, schema, includeUndefinedValues)).toEqual( + expected, + ); }); test('computeDefaults', () => { @@ -1052,9 +1051,8 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType rawFormData, schema, includeUndefinedValues, - false, - experimental_defaultFormStateBehavior - ) + experimental_defaultFormStateBehavior, + ), ).toEqual({ localConst: 'local', RootConst: { @@ -1134,9 +1132,8 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType rawFormData, schema, includeUndefinedValues, - false, - experimental_defaultFormStateBehavior - ) + experimental_defaultFormStateBehavior, + ), ).toEqual({ fromFormData: 'fromFormData', }); @@ -1302,9 +1299,8 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType rawFormData, schema, undefined, - false, - experimental_defaultFormStateBehavior - ) + experimental_defaultFormStateBehavior, + ), ).toEqual(expected); }); @@ -1407,9 +1403,8 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType undefined, schema, undefined, - false, - experimental_defaultFormStateBehavior - ) + experimental_defaultFormStateBehavior, + ), ).toEqual(expected); }); @@ -1455,9 +1450,8 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType undefined, schema, undefined, - false, - experimental_defaultFormStateBehavior - ) + experimental_defaultFormStateBehavior, + ), ).toEqual(expected); }); @@ -1503,9 +1497,8 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType undefined, schema, undefined, - false, - experimental_defaultFormStateBehavior - ) + experimental_defaultFormStateBehavior, + ), ).toEqual(expected); }); @@ -1566,9 +1559,8 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType rawFormData, schema, undefined, - false, - experimental_defaultFormStateBehavior - ) + experimental_defaultFormStateBehavior, + ), ).toEqual(expected); }); @@ -1619,8 +1611,8 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType const expected = ['Raphael', 'Michaelangelo', 'Unknown', 'Unknown']; test('getDefaultFormState', () => { - expect(getDefaultFormState(testValidator, schema, undefined, schema, false, includeUndefinedValues)).toEqual( - expected + expect(getDefaultFormState(testValidator, schema, undefined, schema, includeUndefinedValues)).toEqual( + expected, ); }); @@ -1678,7 +1670,6 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType schema, undefined, schema, - false, includeUndefinedValues, experimental_defaultFormStateBehavior, ), @@ -1742,8 +1733,8 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType const expected: undefined[] = [undefined, undefined, undefined, undefined]; test('getDefaultFormState', () => { - expect(getDefaultFormState(testValidator, schema, undefined, schema, false, includeUndefinedValues)).toEqual( - expected + expect(getDefaultFormState(testValidator, schema, undefined, schema, includeUndefinedValues)).toEqual( + expected, ); }); @@ -1789,8 +1780,8 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType const expected = ['ConstFromRoot', 'ConstFromRoot', 'Constant', 'Constant']; test('getDefaultFormState', () => { - expect(getDefaultFormState(testValidator, schema, undefined, schema, false, includeUndefinedValues)).toEqual( - expected + expect(getDefaultFormState(testValidator, schema, undefined, schema, includeUndefinedValues)).toEqual( + expected, ); }); @@ -1841,7 +1832,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType const expected: never[] = []; test('getDefaultFormState', () => { - expect(getDefaultFormState(testValidator, schema, undefined, schema, false, includeUndefinedValues)).toEqual( + expect(getDefaultFormState(testValidator, schema, undefined, schema, includeUndefinedValues)).toEqual( expected, ); }); @@ -4151,7 +4142,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType }, }; expect( - getDefaultFormState(testValidator, schema, {}, undefined, undefined, false, { + getDefaultFormState(testValidator, schema, {}, undefined, undefined, { emptyObjectFields: 'populateRequiredDefaults', }), ).toEqual({ name: {} }); @@ -4182,7 +4173,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType }, }; expect( - getDefaultFormState(testValidator, schema, {}, undefined, undefined, false, { + getDefaultFormState(testValidator, schema, {}, undefined, undefined, { emptyObjectFields: 'populateRequiredDefaults', }), ).toEqual({ @@ -4216,7 +4207,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType ], }; expect( - getDefaultFormState(testValidator, schema, {}, undefined, undefined, false, { + getDefaultFormState(testValidator, schema, {}, undefined, undefined, { emptyObjectFields: 'populateRequiredDefaults', }), ).toEqual({ foo: 'fooVal', baz: 'bazIsRequired' }); @@ -4394,7 +4385,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType }, }; expect( - getDefaultFormState(testValidator, schema, {}, undefined, undefined, false, { + getDefaultFormState(testValidator, schema, {}, undefined, undefined, { emptyObjectFields: 'populateRequiredDefaults', }), ).toEqual({ name: {} }); @@ -4425,7 +4416,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType }, }; expect( - getDefaultFormState(testValidator, schema, {}, undefined, undefined, false, { + getDefaultFormState(testValidator, schema, {}, undefined, undefined, { emptyObjectFields: 'populateRequiredDefaults', }), ).toEqual({ @@ -4459,7 +4450,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType ], }; expect( - getDefaultFormState(testValidator, schema, {}, undefined, undefined, false, { + getDefaultFormState(testValidator, schema, {}, undefined, undefined, { emptyObjectFields: 'populateRequiredDefaults', }), ).toEqual({ foo: 'fooVal', baz: 'bazIsRequired' }); @@ -4977,7 +4968,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType it('returns field value of default when formData has undefined for field and `useDefaultIfFormDataUndefined`', () => { const formData = { field: undefined }; expect( - getDefaultFormState(testValidator, schema, formData, undefined, undefined, false, { + getDefaultFormState(testValidator, schema, formData, undefined, undefined, { mergeDefaultsIntoFormData: 'useDefaultIfFormDataUndefined', }), ).toEqual(defaultedFormData); @@ -5025,7 +5016,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType }, }; expect( - getDefaultFormState(testValidator, schema, { requiredArray: ['raw0'] }, schema, false, false, { + getDefaultFormState(testValidator, schema, { requiredArray: ['raw0'] }, schema, false, { arrayMinItems: { mergeExtraDefaults: true }, }), ).toEqual({ @@ -5045,7 +5036,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType required: ['requiredArray'], }; expect( - getDefaultFormState(testValidator, schema, undefined, schema, false, false, { + getDefaultFormState(testValidator, schema, undefined, schema, false, { arrayMinItems: { populate: 'requiredOnly' }, }), ).toEqual({ requiredArray: ['default0', 'default0'] }); @@ -5063,7 +5054,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType required: ['requiredArray'], }; expect( - getDefaultFormState(testValidator, schema, { requiredArray: ['raw0'] }, schema, false, false, { + getDefaultFormState(testValidator, schema, { requiredArray: ['raw0'] }, schema, false, { arrayMinItems: { populate: 'requiredOnly' }, }), ).toEqual({ requiredArray: ['raw0'] }); @@ -5081,7 +5072,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType required: ['requiredArray'], }; expect( - getDefaultFormState(testValidator, schema, { requiredArray: ['raw0'] }, schema, false, false, { + getDefaultFormState(testValidator, schema, { requiredArray: ['raw0'] }, schema, false, { arrayMinItems: { populate: 'requiredOnly', mergeExtraDefaults: true }, }), ).toEqual({ requiredArray: ['raw0', 'default0'] }); @@ -5104,7 +5095,7 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType required: ['stringArray', 'numberArray'], }; expect( - getDefaultFormState(testValidator, schema, {}, schema, false, false, { + getDefaultFormState(testValidator, schema, {}, schema, false, { arrayMinItems: { computeSkipPopulate: (_, schema) => !Array.isArray(schema?.items) && typeof schema?.items !== 'boolean' && schema?.items?.type === 'number', diff --git a/packages/validator-ajv8/src/processRawValidationErrors.ts b/packages/validator-ajv8/src/processRawValidationErrors.ts index 8da74ccba5..9906f8e43a 100644 --- a/packages/validator-ajv8/src/processRawValidationErrors.ts +++ b/packages/validator-ajv8/src/processRawValidationErrors.ts @@ -159,7 +159,7 @@ export default function processRawValidationErrors< } // Include form data with undefined values, which is required for custom validation. - const newFormData = getDefaultFormState(validator, schema, formData, schema, false, true) as T; + const newFormData = getDefaultFormState(validator, schema, formData, schema, true) as T; const errorHandler = customValidate(newFormData, createErrorHandler(newFormData), uiSchema); const userErrorSchema = unwrapErrorHandler(errorHandler); From e9fdc91ecf6b9db49f27aa89757e1fe108123ebc Mon Sep 17 00:00:00 2001 From: Piotr-Debicki <105816286+Piotr-Debicki@users.noreply.github.com> Date: Mon, 29 Sep 2025 22:19:58 -0400 Subject: [PATCH 12/15] update documentation --- packages/docs/docs/api-reference/utility-functions.md | 2 +- packages/utils/src/schema/getDefaultFormState.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/docs/docs/api-reference/utility-functions.md b/packages/docs/docs/api-reference/utility-functions.md index 25aae58ccc..6b8fe1b956 100644 --- a/packages/docs/docs/api-reference/utility-functions.md +++ b/packages/docs/docs/api-reference/utility-functions.md @@ -1045,10 +1045,10 @@ Returns the superset of `formData` that includes the given set updated to includ - theSchema: S - The schema for which the default state is desired - [formData]: T | undefined - The current formData, if any, onto which to provide any missing defaults - [rootSchema]: S | undefined - The root schema, used to primarily to look up `$ref`s -- [initialDefaultsGenerated]: boolean - Flag indicating whether the initial form defaults have been generated - [includeUndefinedValues=false]: boolean | "excludeObjectChildren" - Optional flag, if true, cause undefined values to be added as defaults. If "excludeObjectChildren", cause undefined values for this object and pass `includeUndefinedValues` as false when computing defaults for any nested object properties. - [experimental_defaultFormStateBehavior]: Experimental_DefaultFormStateBehavior - See `Form` documentation for the [experimental_defaultFormStateBehavior](./form-props.md#experimental_defaultFormStateBehavior) prop - [experimental_customMergeAllOf]: Experimental_CustomMergeAllOf<S> - See `Form` documentation for the [experimental_customMergeAllOf](./form-props.md#experimental_custommergeallof) prop +- [initialDefaultsGenerated]: boolean - Optional flag, indicates whether or not initial defaults have been generated #### Returns diff --git a/packages/utils/src/schema/getDefaultFormState.ts b/packages/utils/src/schema/getDefaultFormState.ts index 8325272e5b..25d73e1a94 100644 --- a/packages/utils/src/schema/getDefaultFormState.ts +++ b/packages/utils/src/schema/getDefaultFormState.ts @@ -737,7 +737,7 @@ export function getDefaultBasedOnSchemaType< * false when computing defaults for any nested object properties. * @param [experimental_defaultFormStateBehavior] Optional configuration object, if provided, allows users to override default form state behavior * @param [experimental_customMergeAllOf] - Optional function that allows for custom merging of `allOf` schemas - * @param initialDefaultsGenerated - Indicates whether or not initial defaults have been generated + * @param initialDefaultsGenerated - Optional flag, indicates whether or not initial defaults have been generated * @returns - The resulting `formData` with all the defaults provided */ export default function getDefaultFormState< From b1f4b6c6eee8e6286ffc384415a7cb986f5f4872 Mon Sep 17 00:00:00 2001 From: Piotr-Debicki <105816286+Piotr-Debicki@users.noreply.github.com> Date: Mon, 29 Sep 2025 22:23:06 -0400 Subject: [PATCH 13/15] update changelog version --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc2db51017..56e06753a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ it according to semantic versioning. For example, if your PR adds a breaking cha should change the heading of the (upcoming) version to include a major version bump. --> -# 5.24.4 +# 6.0.0-beta.20 ## @rjsf/core From 64e0dffb0ecdebfc08f7aad63e076fe29de000f2 Mon Sep 17 00:00:00 2001 From: Piotr-Debicki <105816286+Piotr-Debicki@users.noreply.github.com> Date: Wed, 1 Oct 2025 18:06:23 -0400 Subject: [PATCH 14/15] rm old descriptions, add change to v6.x upgrade guide.md --- CHANGELOG.md | 8 - .../migration-guides/v6.x upgrade guide.md | 4 + packages/validator-ajv6/src/validator.ts | 199 ------------------ 3 files changed, 4 insertions(+), 207 deletions(-) delete mode 100644 packages/validator-ajv6/src/validator.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 56e06753a6..eff26a9d88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,14 +31,6 @@ should change the heading of the (upcoming) version to include a major version b ## @rjsf/docs - Updated docs for `getDefaultFormState` to reflect addition of `initialDefaultsGenerated` prop -## @rjsf/validator-ajv6 - -- Updated `getDefaultFormState` calls to reflect addition of `initialDefaultsGenerated` - -## @rjsf/validator-ajv8 - -- Updated `getDefaultFormState` calls to reflect addition of `initialDefaultsGenerated` - # 6.0.0-beta.19 ## @rjsf/core diff --git a/packages/docs/docs/migration-guides/v6.x upgrade guide.md b/packages/docs/docs/migration-guides/v6.x upgrade guide.md index 88df4e5cea..f3e1b046bf 100644 --- a/packages/docs/docs/migration-guides/v6.x upgrade guide.md +++ b/packages/docs/docs/migration-guides/v6.x upgrade guide.md @@ -605,6 +605,10 @@ Three new validator-based utility functions are available in `@rjsf/utils`: - `findSelectedOptionInXxxOf(validator: ValidatorType, rootSchema: S, schema: S, fallbackField: string,xxx: 'anyOf' | 'oneOf', formData?: T, experimental_customMergeAllOf?: Experimental_CustomMergeAllOf): S | undefined`: Finds the option that matches the selector field in the `schema` or undefined if nothing is selected - `getFromSchema(validator: ValidatorType, rootSchema: S, schema: S, path: string | string[], defaultValue: T | S, experimental_customMergeAllOf?: Experimental_CustomMergeAllOf): T | S`: Helper that acts like lodash's `get` but additionally retrieves `$ref`s as needed to get the path for schemas +### Changes to existing utility functions + +- `getDefaultFormState`: Added optional `initialDefaultsGenerated` boolean flag that indicates whether or not initial defaults have been generated + ### Dynamic UI Schema for Array Items RJSF 6.x introduces a new feature that allows dynamic UI schema generation for array items. diff --git a/packages/validator-ajv6/src/validator.ts b/packages/validator-ajv6/src/validator.ts deleted file mode 100644 index 5b8f0bcd6b..0000000000 --- a/packages/validator-ajv6/src/validator.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { Ajv, ErrorObject } from 'ajv'; -import { - createErrorHandler, - CustomValidator, - deepEquals, - ErrorSchema, - ErrorTransformer, - FormContextType, - getDefaultFormState, - RJSFSchema, - RJSFValidationError, - ROOT_SCHEMA_PREFIX, - StrictRJSFSchema, - toErrorList, - toErrorSchema, - UiSchema, - unwrapErrorHandler, - ValidationData, - validationDataMerge, - ValidatorType, - withIdRefPrefix, -} from '@rjsf/utils'; - -import { CustomValidatorOptionsType } from './types'; -import createAjvInstance from './createAjvInstance'; - -/** `ValidatorType` implementation that uses the AJV 6 validation mechanism. - * - * @deprecated in favor of the `@rjsf/validator-ajv8 - */ -export default class AJV6Validator - implements ValidatorType -{ - /** The AJV instance to use for all validations - */ - ajv: Ajv; - - /** Constructs an `AJV6Validator` instance using the `options` - * - * @param options - The `CustomValidatorOptionsType` options that are used to create the AJV instance - */ - constructor(options: CustomValidatorOptionsType) { - const { additionalMetaSchemas, customFormats, ajvOptionsOverrides } = options; - this.ajv = createAjvInstance(additionalMetaSchemas, customFormats, ajvOptionsOverrides); - } - - /** Converts an `errorSchema` into a list of `RJSFValidationErrors` - * - * @param errorSchema - The `ErrorSchema` instance to convert - * @param [fieldPath=[]] - The current field path, defaults to [] if not specified - * @deprecated - Use the `toErrorList()` function provided by `@rjsf/utils` instead. This function will be removed in - * the next major release. - */ - toErrorList(errorSchema?: ErrorSchema, fieldPath: string[] = []) { - return toErrorList(errorSchema, fieldPath); - } - - /** Transforming the error output from ajv to format used by @rjsf/utils. - * At some point, components should be updated to support ajv. - * - * @param errors - The list of AJV errors to convert to `RJSFValidationErrors` - * @private - */ - private transformRJSFValidationErrors(errors: ErrorObject[] = []): RJSFValidationError[] { - return errors.map((e: ErrorObject) => { - const { dataPath, keyword, message, params, schemaPath } = e; - const property = `${dataPath}`; - - // put data in expected format - return { - name: keyword, - property, - message, - params, // specific to ajv - stack: `${property} ${message}`.trim(), - schemaPath, - }; - }); - } - - /** Runs the pure validation of the `schema` and `formData` without any of the RJSF functionality. Provided for use - * by the playground. Returns the `errors` from the validation - * - * @param schema - The schema against which to validate the form data * @param schema - * @param formData - The form data to validate - */ - rawValidation(schema: RJSFSchema, formData?: T): { errors?: Result[]; validationError?: Error } { - let validationError: Error | undefined = undefined; - try { - this.ajv.validate(schema, formData); - } catch (err) { - validationError = err as Error; - } - - const errors = this.ajv.errors || undefined; - - // Clear errors to prevent persistent errors, see #1104 - this.ajv.errors = null; - - return { errors: errors as unknown as Result[], validationError }; - } - - /** This function processes the `formData` with an optional user contributed `customValidate` function, which receives - * the form data and a `errorHandler` function that will be used to add custom validation errors for each field. Also - * supports a `transformErrors` function that will take the raw AJV validation errors, prior to custom validation and - * transform them in what ever way it chooses. - * - * @param formData - The form data to validate - * @param schema - The schema against which to validate the form data - * @param [customValidate] - An optional function that is used to perform custom validation - * @param [transformErrors] - An optional function that is used to transform errors after AJV validation - * @param [uiSchema] - An optional uiSchema that is passed to `transformErrors` and `customValidate` - */ - validateFormData( - formData: T | undefined, - schema: S, - customValidate?: CustomValidator, - transformErrors?: ErrorTransformer, - uiSchema?: UiSchema - ): ValidationData { - const rootSchema = schema; - - const rawErrors = this.rawValidation(schema, formData); - const { validationError } = rawErrors; - let errors = this.transformRJSFValidationErrors(rawErrors.errors); - - const noProperMetaSchema = - validationError && validationError.message && validationError.message.includes('no schema with key or ref '); - - if (noProperMetaSchema) { - errors = [...errors, { stack: validationError!.message }]; - } - if (typeof transformErrors === 'function') { - errors = transformErrors(errors, uiSchema); - } - - let errorSchema = toErrorSchema(errors); - - if (noProperMetaSchema) { - errorSchema = { - ...errorSchema, - ...{ - $schema: { - __errors: [validationError!.message], - }, - }, - }; - } - - if (typeof customValidate !== 'function') { - return { errors, errorSchema }; - } - - // Include form data with undefined values, which is required for custom validation. - const newFormData = getDefaultFormState(this, schema, formData, rootSchema, false, true) as T; - - const errorHandler = customValidate(newFormData, createErrorHandler(newFormData), uiSchema); - const userErrorSchema = unwrapErrorHandler(errorHandler); - return validationDataMerge({ errors, errorSchema }, userErrorSchema); - } - - /** - * This function checks if a schema needs to be added and if the root schemas don't match it removes the old root schema from the ajv instance and adds the new one. - * @param rootSchema - The root schema used to provide $ref resolutions - */ - handleSchemaUpdate(rootSchema: RJSFSchema): void { - const rootSchemaId = ROOT_SCHEMA_PREFIX; - // add the rootSchema ROOT_SCHEMA_PREFIX as id. - // if schema validator instance doesn't exist, add it. - // else 'handleRootSchemaChange' should be called if the root schema changes so we don't have to remove and recompile the schema every run. - if (this.ajv.getSchema(ROOT_SCHEMA_PREFIX) === undefined) { - this.ajv.addSchema(rootSchema, ROOT_SCHEMA_PREFIX); - } else if (!deepEquals(rootSchema, this.ajv.getSchema(ROOT_SCHEMA_PREFIX)?.schema)) { - this.ajv.removeSchema(rootSchemaId); - this.ajv.addSchema(rootSchema, rootSchemaId); - } - } - - /** Validates data against a schema, returning true if the data is valid, or - * false otherwise. If the schema is invalid, then this function will return - * false. - * - * @param schema - The schema against which to validate the form data * @param schema - * @param formData- - The form data to validate - * @param rootSchema - The root schema used to provide $ref resolutions - */ - isValid(schema: RJSFSchema, formData: T | undefined, rootSchema: RJSFSchema) { - try { - this.handleSchemaUpdate(rootSchema); - // then rewrite the schema ref's to point to the rootSchema - // this accounts for the case where schema have references to models - // that lives in the rootSchema but not in the schema in question. - const result = this.ajv.validate(withIdRefPrefix(schema), formData); - return result as boolean; - } catch (e) { - return false; - } - } -} From d2694db6c99661bb0c75ee2fc47a5c17b7a94f66 Mon Sep 17 00:00:00 2001 From: Piotr-Debicki <105816286+Piotr-Debicki@users.noreply.github.com> Date: Wed, 1 Oct 2025 20:00:27 -0400 Subject: [PATCH 15/15] Update CHANGELOG.md Co-authored-by: Heath C <51679588+heath-freenome@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eff26a9d88..20133dbf13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ it according to semantic versioning. For example, if your PR adds a breaking cha should change the heading of the (upcoming) version to include a major version bump. --> -# 6.0.0-beta.20 +# 6.0.0-beta.21 ## @rjsf/core