Skip to content

Commit 4e8236f

Browse files
Fix 4819 by modifying the id of the outer FieldComponent render (#4823)
Fixed #4819 by modifying the id for the outer `FieldComponent` render when a `XxxOfField` component is also being rendered - In `@rjsf/core`, updated `SchemaField` to add a new optional property `childFieldPathId` to the `FieldComponent` render to prevent duplicate ids - Also updated `ObjectField` and `ArrayField` to make children use the `childFieldPathId` if present, falling back to the `fieldPathId` if not - Fixed the places in the `XxxOf` tests where the new outer id changed the `description` ids the test was looking for - Updated `CHANGELOG.md` accordingly # Conflicts: # CHANGELOG.md
1 parent 0cbd477 commit 4e8236f

File tree

6 files changed

+57
-29
lines changed

6 files changed

+57
-29
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ should change the heading of the (upcoming) version to include a major version b
1717
-->
1818
# 6.0.0
1919

20+
## @rjsf/core
21+
22+
- Updated `SchemaField` to add a new optional property `childFieldPathId` to the `FieldComponent` render to prevent duplicate ids, fixing (#4819)[https://github.com/rjsf-team/react-jsonschema-form/issues/4819]
23+
- Also updated `ObjectField` and `ArrayField` to make children use the `childFieldPathId` if present, falling back to the `fieldPathId` if not
24+
2025
## Dev / docs / playground
2126

2227
- Updated the libraries to the latest ones that aren't problematic

packages/core/src/components/fields/ArrayField.tsx

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,11 @@ function NormalArray<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends
567567
const canAdd = canAddItem<T, S, F>(registry, schema, formData, uiSchema) && (!renderOptionalField || hasFormData);
568568
const actualFormData = hasFormData ? keyedFormData : [];
569569
const extraClass = renderOptionalField ? ' rjsf-optional-array-field' : '';
570-
const optionalDataControl = renderOptionalField ? <OptionalDataControlsField {...props} /> : undefined;
570+
// All the children will use childFieldPathId if present in the props, falling back to the fieldPathId
571+
const childFieldPathId = props.childFieldPathId ?? fieldPathId;
572+
const optionalDataControl = renderOptionalField ? (
573+
<OptionalDataControlsField {...props} fieldPathId={childFieldPathId} />
574+
) : undefined;
571575
const arrayProps: ArrayFieldTemplateProps<T[], S, F> = {
572576
canAdd,
573577
items: actualFormData.map((keyedItem, index: number) => {
@@ -576,7 +580,7 @@ function NormalArray<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends
576580
const itemCast = item as unknown as T[];
577581
const itemSchema = schemaUtils.retrieveSchema(_schemaItems, itemCast);
578582
const itemErrorSchema = errorSchema ? (errorSchema[index] as ErrorSchema<T[]>) : undefined;
579-
const itemFieldPathId = toFieldPathId(index, globalFormOptions, fieldPathId);
583+
const itemFieldPathId = toFieldPathId(index, globalFormOptions, childFieldPathId);
580584

581585
// Compute the item UI schema using the helper method
582586
const itemUiSchema = computeItemUiSchema<T, S, F>(uiSchema, item, index, formContext);
@@ -675,14 +679,18 @@ function FixedArray<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends
675679
const additionalSchema = isObject(schema.additionalItems)
676680
? schemaUtils.retrieveSchema(schema.additionalItems as S, formData)
677681
: null;
682+
// All the children will use childFieldPathId if present in the props, falling back to the fieldPathId
683+
const childFieldPathId = props.childFieldPathId ?? fieldPathId;
678684

679685
if (items.length < itemSchemas.length) {
680686
// to make sure at least all fixed items are generated
681687
items = items.concat(new Array(itemSchemas.length - items.length));
682688
}
683689
const actualFormData = hasFormData ? keyedFormData : [];
684690
const extraClass = renderOptionalField ? ' rjsf-optional-array-field' : '';
685-
const optionalDataControl = renderOptionalField ? <OptionalDataControlsField {...props} /> : undefined;
691+
const optionalDataControl = renderOptionalField ? (
692+
<OptionalDataControlsField {...props} fieldPathId={childFieldPathId} />
693+
) : undefined;
686694

687695
// These are the props passed into the render function
688696
const canAdd =
@@ -704,7 +712,7 @@ function FixedArray<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends
704712
(additional && isObject(schema.additionalItems)
705713
? schemaUtils.retrieveSchema(schema.additionalItems as S, itemCast)
706714
: itemSchemas[index]) || {};
707-
const itemFieldPathId = toFieldPathId(index, globalFormOptions, fieldPathId);
715+
const itemFieldPathId = toFieldPathId(index, globalFormOptions, childFieldPathId);
708716
// Compute the item UI schema - handle both static and dynamic cases
709717
let itemUiSchema: UiSchema<T[], S, F> | undefined;
710718
if (additional) {
@@ -834,6 +842,8 @@ export default function ArrayField<T = any, S extends StrictRJSFSchema = RJSFSch
834842
const { schema, uiSchema, errorSchema, fieldPathId, registry, formData, onChange } = props;
835843
const { schemaUtils, translateString } = registry;
836844
const { keyedFormData, updateKeyedFormData } = useKeyedFormData<T>(formData);
845+
// All the children will use childFieldPathId if present in the props, falling back to the fieldPathId
846+
const childFieldPathId = props.childFieldPathId ?? fieldPathId;
837847

838848
/** Callback handler for when the user clicks on the add or add at index buttons. Creates a new row of keyed form data
839849
* either at the end of the list (when index is not specified) or inserted at the `index` when it is, adding it into
@@ -871,9 +881,9 @@ export default function ArrayField<T = any, S extends StrictRJSFSchema = RJSFSch
871881
} else {
872882
newKeyedFormData.push(newKeyedFormDataRow);
873883
}
874-
onChange(updateKeyedFormData(newKeyedFormData), fieldPathId.path, newErrorSchema as ErrorSchema<T[]>);
884+
onChange(updateKeyedFormData(newKeyedFormData), childFieldPathId.path, newErrorSchema as ErrorSchema<T[]>);
875885
},
876-
[keyedFormData, registry, schema, onChange, updateKeyedFormData, errorSchema, fieldPathId],
886+
[keyedFormData, registry, schema, onChange, updateKeyedFormData, errorSchema, childFieldPathId],
877887
);
878888

879889
/** Callback handler for when the user clicks on the copy button on an existing array element. Clones the row of
@@ -911,9 +921,9 @@ export default function ArrayField<T = any, S extends StrictRJSFSchema = RJSFSch
911921
} else {
912922
newKeyedFormData.push(newKeyedFormDataRow);
913923
}
914-
onChange(updateKeyedFormData(newKeyedFormData), fieldPathId.path, newErrorSchema as ErrorSchema<T[]>);
924+
onChange(updateKeyedFormData(newKeyedFormData), childFieldPathId.path, newErrorSchema as ErrorSchema<T[]>);
915925
},
916-
[keyedFormData, onChange, updateKeyedFormData, errorSchema, fieldPathId],
926+
[keyedFormData, onChange, updateKeyedFormData, errorSchema, childFieldPathId],
917927
);
918928

919929
/** Callback handler for when the user clicks on the remove button on an existing array element. Removes the row of
@@ -941,9 +951,9 @@ export default function ArrayField<T = any, S extends StrictRJSFSchema = RJSFSch
941951
}
942952
}
943953
const newKeyedFormData = keyedFormData.filter((_, i) => i !== index);
944-
onChange(updateKeyedFormData(newKeyedFormData), fieldPathId.path, newErrorSchema as ErrorSchema<T[]>);
954+
onChange(updateKeyedFormData(newKeyedFormData), childFieldPathId.path, newErrorSchema as ErrorSchema<T[]>);
945955
},
946-
[keyedFormData, onChange, updateKeyedFormData, errorSchema, fieldPathId],
956+
[keyedFormData, onChange, updateKeyedFormData, errorSchema, childFieldPathId],
947957
);
948958

949959
/** Callback handler for when the user clicks on one of the move item buttons on an existing array element. Moves the
@@ -985,9 +995,9 @@ export default function ArrayField<T = any, S extends StrictRJSFSchema = RJSFSch
985995
return _newKeyedFormData;
986996
}
987997
const newKeyedFormData = reOrderArray();
988-
onChange(updateKeyedFormData(newKeyedFormData), fieldPathId.path, newErrorSchema as ErrorSchema<T[]>);
998+
onChange(updateKeyedFormData(newKeyedFormData), childFieldPathId.path, newErrorSchema as ErrorSchema<T[]>);
989999
},
990-
[keyedFormData, onChange, updateKeyedFormData, errorSchema, fieldPathId],
1000+
[keyedFormData, onChange, updateKeyedFormData, errorSchema, childFieldPathId],
9911001
);
9921002

9931003
/** Callback handler used to deal with changing the value of the data in the array at the `index`. Calls the
@@ -1012,9 +1022,9 @@ export default function ArrayField<T = any, S extends StrictRJSFSchema = RJSFSch
10121022
/** Callback handler used to change the value for a checkbox */
10131023
const onSelectChange = useCallback(
10141024
(value: any) => {
1015-
onChange(value, fieldPathId.path, undefined, fieldPathId?.[ID_KEY]);
1025+
onChange(value, childFieldPathId.path, undefined, childFieldPathId?.[ID_KEY]);
10161026
},
1017-
[onChange, fieldPathId],
1027+
[onChange, childFieldPathId],
10181028
);
10191029

10201030
if (!(ITEMS_KEY in schema)) {
@@ -1044,16 +1054,16 @@ export default function ArrayField<T = any, S extends StrictRJSFSchema = RJSFSch
10441054
};
10451055
if (schemaUtils.isMultiSelect(schema)) {
10461056
// If array has enum or uniqueItems set to true, call renderMultiSelect() to render the default multiselect widget or a custom widget, if specified.
1047-
return <ArrayAsMultiSelect<T, S, F> {...props} onSelectChange={onSelectChange} />;
1057+
return <ArrayAsMultiSelect<T, S, F> {...props} fieldPathId={childFieldPathId} onSelectChange={onSelectChange} />;
10481058
}
10491059
if (isCustomWidget<T[], S, F>(uiSchema)) {
1050-
return <ArrayAsCustomWidget<T, S, F> {...props} onSelectChange={onSelectChange} />;
1060+
return <ArrayAsCustomWidget<T, S, F> {...props} fieldPathId={childFieldPathId} onSelectChange={onSelectChange} />;
10511061
}
10521062
if (isFixedItems(schema)) {
10531063
return <FixedArray<T, S, F> {...props} {...arrayProps} />;
10541064
}
10551065
if (schemaUtils.isFilesArray(schema, uiSchema)) {
1056-
return <ArrayAsFiles {...props} onSelectChange={onSelectChange} />;
1066+
return <ArrayAsFiles {...props} fieldPathId={childFieldPathId} onSelectChange={onSelectChange} />;
10571067
}
10581068
return <NormalArray<T, S, F> {...props} {...arrayProps} />;
10591069
}

packages/core/src/components/fields/ObjectField.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
221221
const uiOptions = getUiOptions<T, S, F>(uiSchema, globalUiOptions);
222222
const { properties: schemaProperties = {} } = schema;
223223
const formDataHash = hashObject(formData || {});
224+
// All the children will use childFieldPathId if present in the props, falling back to the fieldPathId
225+
const childFieldPathId = props.childFieldPathId ?? fieldPathId;
224226

225227
const templateTitle = uiOptions.title ?? schema.title ?? title ?? name;
226228
const description = uiOptions.description ?? schema.description;
@@ -288,8 +290,8 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
288290
set(newFormData as GenericObjectType, newKey, newValue);
289291
}
290292

291-
onChange(newFormData, fieldPathId.path);
292-
}, [formData, onChange, registry, fieldPathId, getAvailableKey, schema]);
293+
onChange(newFormData, childFieldPathId.path);
294+
}, [formData, onChange, registry, childFieldPathId, getAvailableKey, schema]);
293295

294296
/** Returns a callback function that deals with the rename of a key for an additional property for a schema. That
295297
* callback will attempt to rename the key and move the existing data to that key, calling `onChange` when it does.
@@ -312,10 +314,10 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
312314
});
313315
const renamedObj = Object.assign({}, ...keyValues);
314316

315-
onChange(renamedObj, fieldPathId.path);
317+
onChange(renamedObj, childFieldPathId.path);
316318
}
317319
},
318-
[formData, onChange, fieldPathId, getAvailableKey],
320+
[formData, onChange, childFieldPathId, getAvailableKey],
319321
);
320322

321323
/** Handles the remove click which removes the old `key` data and calls the `onChange` callback with it
@@ -324,9 +326,9 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
324326
(key: string) => {
325327
const copiedFormData = { ...formData } as T;
326328
unset(copiedFormData, key);
327-
onChange(copiedFormData, fieldPathId.path);
329+
onChange(copiedFormData, childFieldPathId.path);
328330
},
329-
[onChange, fieldPathId, formData],
331+
[onChange, childFieldPathId, formData],
330332
);
331333

332334
if (!renderOptionalField || hasFormData) {
@@ -349,7 +351,7 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
349351

350352
const Template = getTemplate<'ObjectFieldTemplate', T, S, F>('ObjectFieldTemplate', registry, uiOptions);
351353
const optionalDataControl = renderOptionalField ? (
352-
<OptionalDataControlsField {...props} schema={schema} />
354+
<OptionalDataControlsField {...props} fieldPathId={childFieldPathId} schema={schema} />
353355
) : undefined;
354356

355357
const templateProps = {
@@ -371,7 +373,7 @@ export default function ObjectField<T = any, S extends StrictRJSFSchema = RJSFSc
371373
schema={get(schema, [PROPERTIES_KEY, name], {}) as S}
372374
uiSchema={fieldUiSchema}
373375
errorSchema={get(errorSchema, name)}
374-
fieldPathId={fieldPathId}
376+
fieldPathId={childFieldPathId}
375377
formData={get(formData, name)}
376378
handleKeyRename={handleKeyRename}
377379
handleRemoveProperty={handleRemoveProperty}

packages/core/src/components/fields/SchemaField.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
descriptionId,
66
ErrorSchema,
77
Field,
8+
FieldPathId,
89
FieldPathList,
910
FieldProps,
1011
FieldTemplateProps,
@@ -20,6 +21,7 @@ import {
2021
shouldRender,
2122
shouldRenderOptionalField,
2223
StrictRJSFSchema,
24+
toFieldPathId,
2325
UI_OPTIONS_KEY,
2426
UIOptionsType,
2527
} from '@rjsf/utils';
@@ -103,7 +105,7 @@ function SchemaFieldRender<T = any, S extends StrictRJSFSchema = RJSFSchema, F e
103105
registry,
104106
wasPropertyKeyModified = false,
105107
} = props;
106-
const { schemaUtils, globalUiOptions, fields } = registry;
108+
const { schemaUtils, globalFormOptions, globalUiOptions, fields } = registry;
107109
const { AnyOfField: _AnyOfField, OneOfField: _OneOfField } = fields;
108110
const uiOptions = getUiOptions<T, S, F>(uiSchema, globalUiOptions);
109111
const FieldTemplate = getTemplate<'FieldTemplate', T, S, F>('FieldTemplate', registry, uiOptions);
@@ -147,6 +149,9 @@ function SchemaFieldRender<T = any, S extends StrictRJSFSchema = RJSFSchema, F e
147149
const isReplacingAnyOrOneOf = uiOptions.field && uiOptions.fieldReplacesAnyOrOneOf === true;
148150
let XxxOfField: Field<T, S, F> | undefined;
149151
let XxxOfOptions: S[] | undefined;
152+
// When rendering the `XxxOfField` we'll need to change the fieldPathId of the main component, remembering the
153+
// fieldPathId of the children for the ObjectField and ArrayField
154+
let fieldPathIdProps: { fieldPathId: FieldPathId; childFieldPathId?: FieldPathId } = { fieldPathId };
150155
if ((ANY_OF_KEY in schema || ONE_OF_KEY in schema) && !isReplacingAnyOrOneOf && !schemaUtils.isSelect(schema)) {
151156
if (schema[ANY_OF_KEY]) {
152157
XxxOfField = _AnyOfField;
@@ -163,6 +168,12 @@ function SchemaFieldRender<T = any, S extends StrictRJSFSchema = RJSFSchema, F e
163168
const isOptionalRender = shouldRenderOptionalField<T, S, F>(registry, schema, required, uiSchema);
164169
const hasFormData = isFormDataAvailable<T>(formData);
165170
displayLabel = displayLabel && (!isOptionalRender || hasFormData);
171+
fieldPathIdProps = {
172+
childFieldPathId: fieldPathId,
173+
// The main FieldComponent will add `XxxOf` onto the fieldPathId to avoid duplication with the rendering of the
174+
// same FieldComponent by the `XxxOfField`
175+
fieldPathId: toFieldPathId('XxxOf', globalFormOptions, fieldPathId),
176+
};
166177
}
167178

168179
const { __errors, ...fieldErrorSchema } = errorSchema || {};
@@ -176,7 +187,7 @@ function SchemaFieldRender<T = any, S extends StrictRJSFSchema = RJSFSchema, F e
176187
<FieldComponent
177188
{...props}
178189
onChange={handleFieldComponentChange}
179-
fieldPathId={fieldPathId}
190+
{...fieldPathIdProps}
180191
schema={schema}
181192
uiSchema={fieldUiSchema}
182193
disabled={disabled}

packages/core/test/anyOf.test.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ describe('anyOf', () => {
6363
expect(node.querySelectorAll('select')).to.have.length.of(1);
6464
expect(node.querySelector('select').id).eql('root__anyof_select');
6565
expect(node.querySelectorAll('span.required')).to.have.length.of(1);
66-
expect(node.querySelectorAll('#root__description')).to.have.length.of(1);
66+
expect(node.querySelectorAll('#root_XxxOf__description')).to.have.length.of(1);
6767
expect(node.querySelectorAll('#root_baz')).to.have.length.of(1);
6868
});
6969

packages/core/test/oneOf.test.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ describe('oneOf', () => {
6262
expect(node.querySelectorAll('select')).to.have.length.of(1);
6363
expect(node.querySelector('select').id).eql('root__oneof_select');
6464
expect(node.querySelectorAll('span.required')).to.have.length.of(1);
65-
expect(node.querySelectorAll('#root__description')).to.have.length.of(1);
65+
expect(node.querySelectorAll('#root_XxxOf__description')).to.have.length.of(1);
6666
expect(node.querySelectorAll('#root_baz')).to.have.length.of(1);
6767
});
6868

0 commit comments

Comments
 (0)