diff --git a/CHANGELOG.md b/CHANGELOG.md index b014249c06..8b50b082dd 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.23.3 + +## @rjsf/utils + +- Fixed issue with assigning default values to formData with deeply nested required properties, fixing [#4399](https://github.com/rjsf-team/react-jsonschema-form/issues/4399) + # 5.23.2 ## @rjsf/core diff --git a/packages/utils/src/schema/getDefaultFormState.ts b/packages/utils/src/schema/getDefaultFormState.ts index f367b2cbf0..ba0cf7bec7 100644 --- a/packages/utils/src/schema/getDefaultFormState.ts +++ b/packages/utils/src/schema/getDefaultFormState.ts @@ -115,11 +115,11 @@ function maybeAddDefaultToObject( // Or if the schema has a const property defined, then we should always return the computedDefault since it's coming from the const. obj[key] = computedDefault; } else if (emptyObjectFields !== 'skipDefaults') { - if (isObject(computedDefault)) { - // If isParentRequired is undefined, then we are at the root level of the schema so defer to the requiredness of - // the field key itself in the `requiredField` list - const isSelfOrParentRequired = isParentRequired === undefined ? requiredFields.includes(key) : isParentRequired; + // If isParentRequired is undefined, then we are at the root level of the schema so defer to the requiredness of + // the field key itself in the `requiredField` list + const isSelfOrParentRequired = isParentRequired === undefined ? requiredFields.includes(key) : isParentRequired; + if (isObject(computedDefault)) { // If emptyObjectFields 'skipEmptyDefaults' store computedDefault if it's a non-empty object(e.g. not {}) if (emptyObjectFields === 'skipEmptyDefaults') { if (!isEmpty(computedDefault)) { @@ -138,11 +138,12 @@ function maybeAddDefaultToObject( } else if ( // Store computedDefault if it's a defined primitive (e.g., true) and satisfies certain conditions // Condition 1: computedDefault is not undefined - // Condition 2: If emptyObjectFields is 'populateAllDefaults' or 'skipEmptyDefaults) or if the key is a required field + // Condition 2: If emptyObjectFields is 'populateAllDefaults' or 'skipEmptyDefaults) + // Or if isSelfOrParentRequired is 'true' and the key is a required field computedDefault !== undefined && (emptyObjectFields === 'populateAllDefaults' || emptyObjectFields === 'skipEmptyDefaults' || - requiredFields.includes(key)) + (isSelfOrParentRequired && requiredFields.includes(key))) ) { obj[key] = computedDefault; } diff --git a/packages/utils/test/schema/getDefaultFormStateTest.ts b/packages/utils/test/schema/getDefaultFormStateTest.ts index cf3ef4cd6a..6cf5da2979 100644 --- a/packages/utils/test/schema/getDefaultFormStateTest.ts +++ b/packages/utils/test/schema/getDefaultFormStateTest.ts @@ -2284,6 +2284,48 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType }) ).toEqual({ requiredProperty: 'foo' }); }); + it('test an object with a required property that has a nested optional property which has a nested required property with default', () => { + const schema: RJSFSchema = { + type: 'object', + properties: { + baseRequiredProperty: { + type: 'object', + properties: { + optionalProperty: { + type: 'object', + properties: { + nestedRequiredProperty: { + type: 'string', + default: '', + }, + }, + required: ['nestedRequiredProperty'], + }, + requiredProperty: { + type: 'string', + default: 'foo', + }, + }, + required: ['requiredProperty'], + }, + baseOptionalProperty: { + type: 'string', + default: 'baseOptionalProperty', + }, + }, + required: ['baseRequiredProperty'], + }; + expect( + computeDefaults(testValidator, schema, { + rootSchema: schema, + experimental_defaultFormStateBehavior: { emptyObjectFields: 'populateRequiredDefaults' }, + }) + ).toEqual({ + baseRequiredProperty: { + requiredProperty: 'foo', + }, + }); + }); it('test an object with an optional property that has a nested required property and includeUndefinedValues', () => { const schema: RJSFSchema = { type: 'object',