Skip to content

Defaults inside dependencies subschemas are not populated by getDefaultFormState #5198

Description

@MatinF

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

- OS:
- Node:
- npm:

Anything else?

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions