Skip to content

Commit 91aec38

Browse files
authored
Bug: schema combinators(allOf, anyOf, oneOf) cannot be modified when defaults were set (#4570)
* Fixed issue with schema combinators(allOf, anyOf, oneOf) could not be modified when defaults were set * fixed failing tests
1 parent 2704547 commit 91aec38

File tree

4 files changed

+366
-100
lines changed

4 files changed

+366
-100
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ should change the heading of the (upcoming) version to include a major version b
3030

3131
- Fixed issue in BaseInputTemplate where input props were passed to `slotProps.htmlInput`, which does not work in MUI v5.
3232

33+
## @rjsf/utils
34+
35+
- Fixed issue with schema combinators(allOf, anyOf, oneOf) could not be modified when defaults were set, fixing [#4555](https://github.com/rjsf-team/react-jsonschema-form/issues/4555)
36+
3337
## Dev / docs / playground
3438

3539
- Updated docs for ArrayFieldItemTemplate to include prop `onCopyIndexClick`, fixing [#4507](https://github.com/rjsf-team/react-jsonschema-form/issues/4507)

packages/core/test/Form.test.jsx

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1261,6 +1261,174 @@ describeRepeated('Form common', (createFormComponent) => {
12611261
sinon.assert.callCount(onChange, 1);
12621262
sinon.assert.callCount(secondOnChange, 1);
12631263
});
1264+
it('should modify an allOf field when the defaults are set', () => {
1265+
const schema = {
1266+
properties: {
1267+
all_of_field: {
1268+
allOf: [
1269+
{
1270+
properties: {
1271+
first: {
1272+
type: 'string',
1273+
},
1274+
},
1275+
},
1276+
{
1277+
properties: {
1278+
second: {
1279+
type: 'string',
1280+
},
1281+
},
1282+
},
1283+
],
1284+
default: {
1285+
second: 'second!',
1286+
},
1287+
},
1288+
},
1289+
type: 'object',
1290+
};
1291+
1292+
const { node, onChange } = createFormComponent({
1293+
schema,
1294+
});
1295+
1296+
const secondInputID = '#root_all_of_field_second';
1297+
expect(node.querySelector(secondInputID).value).to.equal('second!');
1298+
1299+
act(() => {
1300+
fireEvent.change(node.querySelector(secondInputID), {
1301+
target: { value: 'changed!' },
1302+
});
1303+
});
1304+
1305+
sinon.assert.calledWithMatch(
1306+
onChange.lastCall,
1307+
{
1308+
formData: {
1309+
all_of_field: {
1310+
second: 'changed!',
1311+
},
1312+
},
1313+
schema,
1314+
},
1315+
'root_all_of_field_second'
1316+
);
1317+
1318+
expect(node.querySelector(secondInputID).value).to.equal('changed!');
1319+
});
1320+
it('should modify an oneOf field when the defaults are set', () => {
1321+
const schema = {
1322+
properties: {
1323+
one_of_field: {
1324+
oneOf: [
1325+
{
1326+
properties: {
1327+
first: {
1328+
type: 'string',
1329+
},
1330+
},
1331+
},
1332+
{
1333+
properties: {
1334+
second: {
1335+
type: 'string',
1336+
},
1337+
},
1338+
},
1339+
],
1340+
default: {
1341+
second: 'second!',
1342+
},
1343+
},
1344+
},
1345+
type: 'object',
1346+
};
1347+
1348+
const { node, onChange } = createFormComponent({
1349+
schema,
1350+
});
1351+
1352+
const secondInputID = '#root_one_of_field_second';
1353+
expect(node.querySelector(secondInputID).value).to.equal('second!');
1354+
1355+
act(() => {
1356+
fireEvent.change(node.querySelector(secondInputID), {
1357+
target: { value: 'changed!' },
1358+
});
1359+
});
1360+
1361+
sinon.assert.calledWithMatch(
1362+
onChange.lastCall,
1363+
{
1364+
formData: {
1365+
one_of_field: {
1366+
second: 'changed!',
1367+
},
1368+
},
1369+
schema,
1370+
},
1371+
'root_one_of_field_second'
1372+
);
1373+
1374+
expect(node.querySelector(secondInputID).value).to.equal('changed!');
1375+
});
1376+
it('should modify an anyOf field when the defaults are set', () => {
1377+
const schema = {
1378+
properties: {
1379+
any_of_field: {
1380+
anyOf: [
1381+
{
1382+
properties: {
1383+
first: {
1384+
type: 'string',
1385+
},
1386+
},
1387+
},
1388+
{
1389+
properties: {
1390+
second: {
1391+
type: 'string',
1392+
},
1393+
},
1394+
},
1395+
],
1396+
default: {
1397+
second: 'second!',
1398+
},
1399+
},
1400+
},
1401+
type: 'object',
1402+
};
1403+
1404+
const { node, onChange } = createFormComponent({
1405+
schema,
1406+
});
1407+
1408+
const secondInputID = '#root_any_of_field_second';
1409+
expect(node.querySelector(secondInputID).value).to.equal('second!');
1410+
1411+
act(() => {
1412+
fireEvent.change(node.querySelector(secondInputID), {
1413+
target: { value: 'changed!' },
1414+
});
1415+
});
1416+
1417+
sinon.assert.calledWithMatch(
1418+
onChange.lastCall,
1419+
{
1420+
formData: {
1421+
any_of_field: {
1422+
second: 'changed!',
1423+
},
1424+
},
1425+
schema,
1426+
},
1427+
'root_any_of_field_second'
1428+
);
1429+
1430+
expect(node.querySelector(secondInputID).value).to.equal('changed!');
1431+
});
12641432
});
12651433

12661434
describe('Blur handler', () => {

packages/utils/src/schema/getDefaultFormState.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,9 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
223223
// For object defaults, only override parent defaults that are defined in
224224
// schema.default.
225225
defaults = mergeObjects(defaults!, schema.default as GenericObjectType) as T;
226-
} else if (DEFAULT_KEY in schema) {
226+
} else if (DEFAULT_KEY in schema && !schema[ANY_OF_KEY] && !schema[ONE_OF_KEY]) {
227+
// If the schema has a default value, then we should use it as the default.
228+
// And if the schema does not have anyOf or oneOf, this is done because we need to merge the defaults with the formData.
227229
defaults = schema.default as unknown as T;
228230
} else if (REF_KEY in schema) {
229231
const refName = schema[REF_KEY];
@@ -284,7 +286,7 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
284286
getClosestMatchingOption<T, S, F>(
285287
validator,
286288
rootSchema,
287-
rawFormData,
289+
rawFormData ?? (schema.default as T),
288290
oneOf as S[],
289291
0,
290292
discriminator,
@@ -302,7 +304,7 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
302304
getClosestMatchingOption<T, S, F>(
303305
validator,
304306
rootSchema,
305-
rawFormData,
307+
rawFormData ?? (schema.default as T),
306308
anyOf as S[],
307309
0,
308310
discriminator,
@@ -347,7 +349,9 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
347349
experimental_defaultFormStateBehavior,
348350
experimental_customMergeAllOf
349351
);
350-
if (!isObject(rawFormData)) {
352+
if (!isObject(rawFormData) || ALL_OF_KEY in schema) {
353+
// If the formData is not an object which means it's a primitive field, then we need to merge the defaults into the formData.
354+
// Or if the schema has allOf, we need to merge the defaults into the formData because we don't compute the defaults for allOf.
351355
defaultsWithFormData = mergeDefaultsWithFormData<T>(
352356
defaultsWithFormData as T,
353357
matchingFormData as T,

0 commit comments

Comments
 (0)