Skip to content

Commit 8772516

Browse files
committed
1 parent 123fc70 commit 8772516

File tree

3 files changed

+216
-12
lines changed

3 files changed

+216
-12
lines changed

.changeset/late-adults-tan.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@sjsf/form": patch
3+
---
4+
5+
Port https://github.com/rjsf-team/react-jsonschema-form/pull/4757

packages/form/src/core/default-state.test.ts

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6297,4 +6297,201 @@ describe("getDefaultFormState2()", () => {
62976297
empty: null,
62986298
});
62996299
});
6300+
6301+
describe("array object reference sharing fix (issue #4756)", () => {
6302+
const schema: Schema = {
6303+
type: "object",
6304+
properties: {
6305+
config: {
6306+
oneOf: [
6307+
{
6308+
title: "List Configuration",
6309+
properties: {
6310+
items: {
6311+
type: "array",
6312+
minItems: 2,
6313+
items: {
6314+
type: "object",
6315+
properties: {
6316+
field: {
6317+
type: "string",
6318+
},
6319+
},
6320+
},
6321+
},
6322+
},
6323+
required: ["items"],
6324+
},
6325+
],
6326+
},
6327+
},
6328+
};
6329+
6330+
it("should create independent object instances for array items via getDefaultFormState", () => {
6331+
const result = getDefaultFormState(
6332+
testValidator,
6333+
defaultMerger,
6334+
schema,
6335+
undefined,
6336+
schema
6337+
) as any;
6338+
6339+
expect(result).toStrictEqual({ config: { items: [{}, {}] } });
6340+
6341+
// Verify that modifying properties of one array item doesn't affect others
6342+
expect(result.config.items).toBeDefined();
6343+
expect(Array.isArray(result.config.items)).toBe(true);
6344+
6345+
// Verify objects are independent instances - modifying one shouldn't affect the other
6346+
(result.config.items[0] as any).field = "test-value-1";
6347+
expect((result.config.items[1] as any).field).toBeUndefined();
6348+
expect(result.config.items[0]).not.toBe(result.config.items[1]);
6349+
});
6350+
6351+
it("should create independent object instances for array items via computeDefaults", () => {
6352+
const result = computeDefaults(testValidator, defaultMerger, schema, {
6353+
...defaults,
6354+
rootSchema: schema,
6355+
}) as any;
6356+
6357+
expect(result).toStrictEqual({ config: { items: [{}, {}] } });
6358+
6359+
// Verify that modifying properties of one array item doesn't affect others
6360+
expect(result.config.items).toBeDefined();
6361+
expect(Array.isArray(result.config.items)).toBe(true);
6362+
6363+
// Verify objects are independent instances - modifying one shouldn't affect the other
6364+
(result.config.items[0] as any).field = "test-value-1";
6365+
expect((result.config.items[1] as any).field).toBeUndefined();
6366+
expect(result.config.items[0]).not.toBe(result.config.items[1]);
6367+
});
6368+
6369+
it("should create independent object instances for array items via getArrayDefaults", () => {
6370+
const arraySchema: Schema = {
6371+
type: "array",
6372+
minItems: 3,
6373+
items: {
6374+
type: "object",
6375+
properties: {
6376+
field: {
6377+
type: "string",
6378+
},
6379+
},
6380+
},
6381+
};
6382+
6383+
const result = getArrayDefaults(
6384+
testValidator,
6385+
defaultMerger,
6386+
arraySchema,
6387+
{
6388+
...defaults,
6389+
rootSchema: arraySchema,
6390+
},
6391+
undefined
6392+
) as any;
6393+
6394+
expect(result).toStrictEqual([{}, {}, {}]);
6395+
6396+
// Verify that array exists and objects are independent
6397+
expect(result).toBeDefined();
6398+
expect(Array.isArray(result)).toBe(true);
6399+
6400+
// Verify objects are independent instances
6401+
(result[0] as any).field = "test-value-1";
6402+
(result[1] as any).field = "test-value-2";
6403+
expect((result[2] as any).field).toBeUndefined();
6404+
expect(result[0]).not.toBe(result[1]);
6405+
expect(result[1]).not.toBe(result[2]);
6406+
expect(result[0]).not.toBe(result[2]);
6407+
});
6408+
6409+
it("should ensure array items with default values are independent instances", () => {
6410+
const arraySchemaWithDefaults: Schema = {
6411+
type: "array",
6412+
minItems: 2,
6413+
items: {
6414+
type: "object",
6415+
properties: {
6416+
field: {
6417+
type: "string",
6418+
default: "default-value",
6419+
},
6420+
},
6421+
},
6422+
};
6423+
6424+
const result = getArrayDefaults(
6425+
testValidator,
6426+
defaultMerger,
6427+
arraySchemaWithDefaults,
6428+
{
6429+
...defaults,
6430+
rootSchema: arraySchemaWithDefaults,
6431+
},
6432+
undefined
6433+
) as any;
6434+
6435+
expect(result).toStrictEqual([
6436+
{ field: "default-value" },
6437+
{ field: "default-value" },
6438+
]);
6439+
6440+
// Verify that array exists and objects are independent
6441+
expect(result).toBeDefined();
6442+
expect(Array.isArray(result)).toBe(true);
6443+
6444+
// Verify objects are independent instances - modifying one shouldn't affect the other
6445+
(result[0] as any).field = "modified-value";
6446+
expect((result[1] as any).field).toBe("default-value");
6447+
expect(result[0]).not.toBe(result[1]);
6448+
});
6449+
6450+
it("should ensure nested objects in arrays are independent instances", () => {
6451+
const nestedObjectSchema: Schema = {
6452+
type: "array",
6453+
minItems: 2,
6454+
items: {
6455+
type: "object",
6456+
properties: {
6457+
nested: {
6458+
type: "object",
6459+
properties: {
6460+
value: {
6461+
type: "string",
6462+
default: "nested-default",
6463+
},
6464+
},
6465+
},
6466+
},
6467+
},
6468+
};
6469+
6470+
const result = getArrayDefaults(
6471+
testValidator,
6472+
defaultMerger,
6473+
nestedObjectSchema,
6474+
{
6475+
...defaults,
6476+
rootSchema: nestedObjectSchema,
6477+
},
6478+
undefined
6479+
) as any;
6480+
6481+
expect(result).toStrictEqual([
6482+
{ nested: { value: "nested-default" } },
6483+
{ nested: { value: "nested-default" } },
6484+
]);
6485+
6486+
// Verify that array exists and nested objects are independent
6487+
expect(result).toBeDefined();
6488+
expect(Array.isArray(result)).toBe(true);
6489+
6490+
// Verify nested objects are independent instances
6491+
(result[0] as any).nested.value = "modified-nested-value";
6492+
expect((result[1] as any).nested.value).toBe("nested-default");
6493+
expect(result[0]).not.toBe(result[1]);
6494+
expect((result[0] as any).nested).not.toBe((result[1] as any).nested);
6495+
});
6496+
});
63006497
});

packages/form/src/core/default-state.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -858,18 +858,20 @@ export function getArrayDefaults(
858858
const fillerDefault = fillerSchema.default;
859859

860860
// Calculate filler entries for remaining items (minItems - existing raw data/defaults)
861-
const fillerEntries = new Array(schema.minItems - defaultsLength).fill(
862-
computeDefaults(validator, merger, fillerSchema, {
863-
parentDefaults: fillerDefault,
864-
rootSchema,
865-
stack: stack,
866-
experimental_defaultFormStateBehavior,
867-
required,
868-
includeUndefinedValues: false,
869-
rawFormData: undefined,
870-
isSchemaRoot: false,
871-
shouldMergeDefaultsIntoFormData,
872-
})
861+
const fillerEntries = Array.from(
862+
{ length: schema.minItems - defaultsLength },
863+
() =>
864+
computeDefaults(validator, merger, fillerSchema, {
865+
parentDefaults: fillerDefault,
866+
rootSchema,
867+
stack: stack,
868+
experimental_defaultFormStateBehavior,
869+
required,
870+
includeUndefinedValues: false,
871+
rawFormData: undefined,
872+
isSchemaRoot: false,
873+
shouldMergeDefaultsIntoFormData,
874+
})
873875
);
874876
// then fill up the rest with either the item default or empty, up to minItems
875877
return defaultsLength ? defaults!.concat(fillerEntries) : fillerEntries;

0 commit comments

Comments
 (0)