Prerequisites
What theme are you using?
antd
Version
6.7.1 / 6.8.0
Current Behavior
We are trying to upgrade our rjsf from a very old release to the latest, but we seem to encounter various bugs this introduces including the below:
The bug we observe consists of two related cases where a default that lives inside a dependencies subschema
never reaches the form data. Both worked in rjsf 5. They share a cause in
computeDefaults() in packages/utils/src/schema/getDefaultFormState.ts, so
they are filed together.
The visible symptom in both cases is a field the user never touched showing a
validation error, and with a oneOf style dependency the error is misleading:
because neither branch matches, ajv reports every branch's failure, so a missing
p also surfaces as .t must be equal to one of the allowed values pointing at
a field whose value is correct.
Case 1: no form data, so the dependency is never resolved
computeDefaults() has a DEPENDENCIES_KEY branch, but it resolves the
dependency against data that is already present. Called with no form data it has
nothing to resolve against, so defaults inside the dependency are dropped.
This is what getNewFormDataRow() in @rjsf/core does when a row is added to an
array:
return schemaUtils.getDefaultFormState(itemSchema);
so a brand new, untouched array row is incomplete and fails validation as soon as
it renders.
Reproduction
import { createSchemaUtils } from '@rjsf/utils';
import validator from '@rjsf/validator-ajv8';
const schema = {
type: 'object',
required: ['t'],
properties: { t: { type: 'integer', default: 0 } },
dependencies: {
t: {
oneOf: [
{ properties: { t: { enum: [0] }, p: { type: 'integer', default: 5 } }, required: ['p'] },
{ properties: { t: { enum: [1] } } },
],
},
},
};
const su = createSchemaUtils(validator, schema);
su.getDefaultFormState(schema, undefined); // -> { t: 0 } wrong
su.getDefaultFormState(schema, { t: 0 }); // -> { t: 0, p: 5 } correct
retrieveSchema(schema, { t: 0 }) already returns the dependency resolved, with
p in both properties and required, so the resolution machinery works. Only
the no form data path in computeDefaults misses it.
In the playground
Paste this schema, leave the form data empty, and press + on the array. The new
row shows must have required property 'p' before anything has been touched.
{
"type": "object",
"properties": {
"rows": {
"type": "array",
"items": {
"type": "object",
"required": ["t"],
"properties": { "t": { "title": "T", "type": "integer", "default": 0 } },
"dependencies": {
"t": {
"oneOf": [
{ "properties": { "t": { "enum": [0] }, "p": { "title": "P", "type": "integer", "default": 5 } }, "required": ["p"] },
{ "properties": { "t": { "enum": [1] } } }
]
}
}
}
}
}
}
Case 2: form data is present, but the array is nested too deeply
Even with form data, dependency defaults are not applied to array items once the
array sits two or more object levels below the root. Plain nested objects are
fine, verified to three levels, and arrays one level down are fine. It is the
combination of depth and array items that fails.
Reproduction
const child = {
type: 'object',
required: ['t'],
properties: { t: { type: 'integer', default: 0 } },
dependencies: {
t: {
oneOf: [
{ properties: { t: { enum: [0] }, p: { type: 'integer', default: 5 } }, required: ['p'] },
{ properties: { t: { enum: [1] } } },
],
},
},
};
const arr = { type: 'array', minItems: 1, items: child };
// array one level down: correct
getDefaultFormState({ type: 'object', properties: { rows: arr } }, { rows: [{ t: 0 }] });
// -> { rows: [{ t: 0, p: 5 }] }
// array two levels down: p is lost
getDefaultFormState(
{ type: 'object', properties: { f: { type: 'object', properties: { rows: arr } } } },
{ f: { rows: [{ t: 0 }] } }
);
// -> { f: { rows: [{ t: 0 }] } }
Calling getDefaultFormState(child, { t: 0 }) on the row on its own still
returns { t: 0, p: 5 }, so only the nested walk loses it.
In the playground
Paste the schema below together with the form data below it, then change T from
A to B and back to A. P stays empty and the row reports
must have required property 'P', even though P has a default of 5. Removing
the f wrapper from the schema makes it work.
{
"type": "object",
"properties": {
"f": {
"type": "object",
"properties": {
"rows": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["t"],
"properties": {
"t": {
"title": "T",
"type": "integer",
"default": 0,
"oneOf": [
{ "title": "A", "enum": [0] },
{ "title": "B", "enum": [1] }
]
}
},
"dependencies": {
"t": {
"oneOf": [
{ "properties": { "t": { "enum": [0] }, "p": { "title": "P", "type": "integer", "default": 5 } }, "required": ["p"] },
{ "properties": { "t": { "enum": [1] } } }
]
}
}
}
}
}
}
}
}
{ "f": { "rows": [{ "t": 1 }] } }
Expected behaviour
A default defined inside a dependencies subschema should be populated
whenever that branch is active, regardless of whether form data was supplied and
regardless of how deeply the owning array is nested. rjsf 5 produced this for the
same schemas.
Expected Behavior
No response
Steps To Reproduce
No response
Environment
Anything else?
No response
Prerequisites
What theme are you using?
antd
Version
6.7.1 / 6.8.0
Current Behavior
We are trying to upgrade our rjsf from a very old release to the latest, but we seem to encounter various bugs this introduces including the below:
The bug we observe consists of two related cases where a
defaultthat lives inside adependenciessubschemanever reaches the form data. Both worked in rjsf 5. They share a cause in
computeDefaults()inpackages/utils/src/schema/getDefaultFormState.ts, sothey are filed together.
The visible symptom in both cases is a field the user never touched showing a
validation error, and with a
oneOfstyle dependency the error is misleading:because neither branch matches, ajv reports every branch's failure, so a missing
palso surfaces as.t must be equal to one of the allowed valuespointing ata field whose value is correct.
Case 1: no form data, so the dependency is never resolved
computeDefaults()has aDEPENDENCIES_KEYbranch, but it resolves thedependency against data that is already present. Called with no form data it has
nothing to resolve against, so defaults inside the dependency are dropped.
This is what
getNewFormDataRow()in@rjsf/coredoes when a row is added to anarray:
so a brand new, untouched array row is incomplete and fails validation as soon as
it renders.
Reproduction
retrieveSchema(schema, { t: 0 })already returns the dependency resolved, withpin bothpropertiesandrequired, so the resolution machinery works. Onlythe no form data path in
computeDefaultsmisses it.In the playground
Paste this schema, leave the form data empty, and press
+on the array. The newrow shows
must have required property 'p'before anything has been touched.{ "type": "object", "properties": { "rows": { "type": "array", "items": { "type": "object", "required": ["t"], "properties": { "t": { "title": "T", "type": "integer", "default": 0 } }, "dependencies": { "t": { "oneOf": [ { "properties": { "t": { "enum": [0] }, "p": { "title": "P", "type": "integer", "default": 5 } }, "required": ["p"] }, { "properties": { "t": { "enum": [1] } } } ] } } } } } }Case 2: form data is present, but the array is nested too deeply
Even with form data, dependency defaults are not applied to array items once the
array sits two or more object levels below the root. Plain nested objects are
fine, verified to three levels, and arrays one level down are fine. It is the
combination of depth and array items that fails.
Reproduction
Calling
getDefaultFormState(child, { t: 0 })on the row on its own stillreturns
{ t: 0, p: 5 }, so only the nested walk loses it.In the playground
Paste the schema below together with the form data below it, then change
TfromAtoBand back toA.Pstays empty and the row reportsmust have required property 'P', even thoughPhas a default of5. Removingthe
fwrapper from the schema makes it work.{ "type": "object", "properties": { "f": { "type": "object", "properties": { "rows": { "type": "array", "minItems": 1, "items": { "type": "object", "required": ["t"], "properties": { "t": { "title": "T", "type": "integer", "default": 0, "oneOf": [ { "title": "A", "enum": [0] }, { "title": "B", "enum": [1] } ] } }, "dependencies": { "t": { "oneOf": [ { "properties": { "t": { "enum": [0] }, "p": { "title": "P", "type": "integer", "default": 5 } }, "required": ["p"] }, { "properties": { "t": { "enum": [1] } } } ] } } } } } } } }{ "f": { "rows": [{ "t": 1 }] } }Expected behaviour
A
defaultdefined inside adependenciessubschema should be populatedwhenever that branch is active, regardless of whether form data was supplied and
regardless of how deeply the owning array is nested. rjsf 5 produced this for the
same schemas.
Expected Behavior
No response
Steps To Reproduce
No response
Environment
Anything else?
No response