-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Description
Prerequisites
- I have searched the existing issues
- I understand that providing a SSCCE example is tremendously useful to the maintainers.
- I have read the documentation
- Ideally, I'm providing a sample JSFiddle, Codesandbox.io or preferably a shared playground link demonstrating the issue.
What theme are you using?
core
Version
6.x
Current Behavior
When using discriminated unions (oneOf
) and switching to a schema that contains an array with minItems
, RJSF creates shared object references for all array items. Modifying properties in one array item affects all other items in the array.
All array items share the same object reference. Changes to any property in one item are reflected across all items in the array.
Expected Behavior
Each array item should be an independent object with its own property values. Modifying one item should not affect other items.
Steps To Reproduce
- Open the reproduction link above
- Modify the "field" value in any of the array items
- Bug: All other array items reflect the same changes
Environment
- OS: MacOS 15.6
- Node: 22.17.1
- npm: 10.9.2
Anything else?
Root Cause
The bug is in packages/utils/src/schema/getDefaultFormState.ts
in the getArrayDefaults
function around line 653:
const fillerEntries: T[] = new Array(schema.minItems - defaultsLength).fill(
computeDefaults<any, S, F>(validator, fillerSchema, {
// ... config
}),
) as T[];
The .fill()
method is called with a single object reference, causing all array positions to reference the same object in memory.
Critical Conditions
This bug is very specific to:
- Discriminated unions (
oneOf
) - Undefined initial form data (not empty arrays)
- Arrays with
minItems
> 0 - Array items that are objects (not primitives)
- Must avoid early return conditions in
getArrayDefaults
: not multiselect, not computeSkipPopulate, minItems > existing defaults length
Suggested Fix
Option 1: Array.from with individual computeDefaults calls (safer)
const fillerEntries: T[] = Array.from({ length: schema.minItems - defaultsLength }, () =>
computeDefaults<any, S, F>(validator, fillerSchema, {
parentDefaults: fillerDefault,
rootSchema,
_recurseList,
experimental_defaultFormStateBehavior,
experimental_customMergeAllOf,
required,
shouldMergeDefaultsIntoFormData,
})
) as T[];
Option 2: structuredClone optimization (potentially unsafe)
const baseDefault = computeDefaults<any, S, F>(validator, fillerSchema, {
parentDefaults: fillerDefault,
rootSchema,
_recurseList,
experimental_defaultFormStateBehavior,
experimental_customMergeAllOf,
required,
shouldMergeDefaultsIntoFormData,
});
const fillerEntries: T[] = Array.from({ length: schema.minItems - defaultsLength }, () =>
structuredClone(baseDefault)
) as T[];
Closest existing issue I could find: #847