Skip to content

Commit 27e6956

Browse files
Fix: made small changes to utility functions to fix #3997, #4314 and #4322 (#4329)
* Fix: made small changes to utility functions to fix #3997 and #4322 Fixes #3997 and #4322 - In `@rjsf/utils`, made the following changes: - Updated `mergeDefaultsWithFormData()` to not overwrite a default when the formData has an undefined value - Updated `getClosestMatchingOption()` to improve the scoring function so that an object container that matches a key gets an extra point - In `@rjsf/core`, updated `MultiSchemaField` to call `onChange` after setting the new option in state rather than before - Updated the `CHANGELOG.md` accordingly * - In order to avoid regressions, added a new `mergeDefaultsWithFormData` prop to the `Experimental_DefaultFormStateBehavior` - Updated `mergeDefaultsWithFormData()` to add new optional `defaultSupercedesUndefined` that when true uses the defaults rather than `undefined` formData - Updated `getDefaultFormState()` to pass true to `mergeDefaultsWithFormData` for `defaultSupercedesUndefined` when `mergeDefaultsIntoFormData` has the value `useDefaultIfFormDataUndefined` - Updated the documentation for the new capabilities - Updated the playground to add controls for the new `mergeDefaultsIntoFormData` option - moved the `Show Error List` component over one column, making it inline radio buttons rather than a select * - Improved documentation a teeny bit * - More doc improvements * - Responded to reviewer feedback * - Fixed broken links * - Added section header for wrapping BaseInputTemplate * - Updated CHANGELOG.md
1 parent f6c5bf7 commit 27e6956

File tree

14 files changed

+158
-21
lines changed

14 files changed

+158
-21
lines changed

CHANGELOG.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,26 @@ should change the heading of the (upcoming) version to include a major version b
1818

1919
# 5.22.0
2020

21+
## @rjsf/core
22+
23+
- Updated `MultiSchemaField` to call the `onChange` handler after setting the new option, fixing [#3997](https://github.com/rjsf-team/react-jsonschema-form/issues/3977) and [#4314](https://github.com/rjsf-team/react-jsonschema-form/issues/4314)
24+
2125
## @rjsf/utils
2226

27+
- Added `experimental_customMergeAllOf` option to `retrieveSchema()` and `getDefaultFormState()` to allow custom merging of `allOf` schemas
2328
- Made fields with const property pre-filled and readonly, fixing [#2600](https://github.com/rjsf-team/react-jsonschema-form/issues/2600)
24-
- Added `experimental_customMergeAllOf` option to `retrieveSchema` to allow custom merging of `allOf` schemas
29+
- Added `mergeDefaultsIntoFormData` option to `Experimental_DefaultFormStateBehavior` type to control how to handle merging of defaults
30+
- Updated `mergeDefaultsWithFormData()` to add new optional `defaultSupercedesUndefined` that when true uses the defaults rather than `undefined` formData, fixing [#4322](https://github.com/rjsf-team/react-jsonschema-form/issues/4322)
31+
- Updated `getDefaultFormState()` to pass true to `mergeDefaultsWithFormData` for `defaultSupercedesUndefined` when `mergeDefaultsIntoFormData` has the value `useDefaultIfFormDataUndefined`, fixing [#4322](https://github.com/rjsf-team/react-jsonschema-form/issues/4322)
32+
- Updated `getClosestMatchingOption()` to improve the scoring of sub-property objects that are provided over ones that aren't, fixing [#3997](https://github.com/rjsf-team/react-jsonschema-form/issues/3977) and [#4314](https://github.com/rjsf-team/react-jsonschema-form/issues/4314)
33+
34+
## Dev / docs / playground
35+
36+
- Updated the `form-props.md` to add documentation for the new `experimental_customMergeAllOf` props and the `experimental_defaultFormStateBehavior.mergeDefaultsIntoFormData` option
37+
- Updated the `utility-functions.md` to add documentation for the new optional `defaultSupercedesUndefined` parameter and the two missing optional fields on `getDefaultFormState()`
38+
- Updated the `custom-templates.md` to add a section header for wrapping `BaseInputTemplate`
39+
- Updated the playground to add controls for the new `mergeDefaultsIntoFormData` option
40+
- In the process, moved the `Show Error List` component over one column, making it inline radio buttons rather than a select
2541

2642
# 5.21.2
2743

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"precommit": "lint-staged",
1515
"publish-to-npm": "npm run build && npm publish",
1616
"test": "jest",
17-
"test:debug": "node --inspect-brk node_modules/.bin/jest",
17+
"test:debug": "node --inspect-brk ../../node_modules/.bin/jest",
1818
"test:update": "jest --u",
1919
"test:watch": "jest --watch",
2020
"test-coverage": "jest --coverage"

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,10 @@ class AnyOfField<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends For
128128
// so that only the root objects themselves are created without adding undefined children properties
129129
newFormData = schemaUtils.getDefaultFormState(newOption, newFormData, 'excludeObjectChildren') as T;
130130
}
131-
onChange(newFormData, undefined, this.getFieldId());
132131

133-
this.setState({ selectedOption: intOption });
132+
this.setState({ selectedOption: intOption }, () => {
133+
onChange(newFormData, undefined, this.getFieldId());
134+
});
134135
};
135136

136137
getFieldId() {

packages/docs/docs/advanced-customization/custom-templates.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,8 @@ render(
362362
);
363363
```
364364

365+
### Wrapping BaseInputTemplate to customize it
366+
365367
Sometimes you just need to pass some additional properties to the existing `BaseInputTemplate`.
366368
The way to do this varies based upon whether you are using `core` or some other theme (such as `mui`):
367369

packages/docs/docs/api-reference/form-props.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,17 @@ render(
251251
);
252252
```
253253

254+
### mergeDefaultsIntoFormData
255+
256+
Optional enumerated flag controlling how the defaults are merged into the form data when dealing with undefined values, defaulting to `useFormDataIfPresent`.
257+
258+
NOTE: If there is a default for a field and the `formData` is unspecified, the default ALWAYS merges.
259+
260+
| Flag Value | Description |
261+
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
262+
| `useFormDataIfPresent` | Legacy behavior - Do not merge defaults if there is a value for a field in `formData` even if that value is explicitly set to `undefined` |
263+
| `useDefaultIfFormDataUndefined` | If the value of a field within the `formData` is `undefined`, then use the default value instead |
264+
254265
## experimental_customMergeAllOf
255266

256267
The `experimental_customMergeAllOf` function allows you to provide a custom implementation for merging `allOf` schemas. This can be particularly useful in scenarios where the default [json-schema-merge-allof](https://github.com/mokkabonna/json-schema-merge-allof) library becomes a performance bottleneck, especially with large and complex schemas or doesn't satisfy your needs.

packages/docs/docs/api-reference/utility-functions.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,7 @@ When merging defaults and form data, we want to merge in this specific way:
575575
- [defaults]: T | undefined - The defaults to merge
576576
- [formData]: T | undefined - The form data into which the defaults will be merged
577577
- [mergeExtraArrayDefaults=false]: boolean - If true, any additional default array entries are appended onto the formData
578+
- [defaultSupercedesUndefined=false]: boolean - If true, an explicit undefined value will be overwritten by the default value
578579

579580
#### Returns
580581

@@ -897,6 +898,8 @@ Returns the superset of `formData` that includes the given set updated to includ
897898
- [formData]: T | undefined - The current formData, if any, onto which to provide any missing defaults
898899
- [rootSchema]: S | undefined - The root schema, used to primarily to look up `$ref`s
899900
- [includeUndefinedValues=false]: boolean | "excludeObjectChildren" - Optional flag, if true, cause undefined values to be added as defaults. If "excludeObjectChildren", cause undefined values for this object and pass `includeUndefinedValues` as false when computing defaults for any nested object properties.
901+
- [experimental_defaultFormStateBehavior]: Experimental_DefaultFormStateBehavior - See `Form` documentation for the [experimental_defaultFormStateBehavior](./form-props.md#experimental_defaultFormStateBehavior) prop
902+
- [experimental_customMergeAllOf]: Experimental_CustomMergeAllOf&lt;S&gt; - See `Form` documentation for the [experimental_customMergeAllOf](./form-props.md#experimental_customMergeAllOf) prop
900903

901904
#### Returns
902905

packages/playground/src/components/Header.tsx

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,18 +63,18 @@ const liveSettingsBooleanSchema: RJSFSchema = {
6363
noValidate: { type: 'boolean', title: 'Disable validation' },
6464
noHtml5Validate: { type: 'boolean', title: 'Disable HTML 5 validation' },
6565
focusOnFirstError: { type: 'boolean', title: 'Focus on 1st Error' },
66-
},
67-
};
68-
69-
const liveSettingsSelectSchema: RJSFSchema = {
70-
type: 'object',
71-
properties: {
7266
showErrorList: {
7367
type: 'string',
7468
default: 'top',
7569
title: 'Show Error List',
7670
enum: [false, 'top', 'bottom'],
7771
},
72+
},
73+
};
74+
75+
const liveSettingsSelectSchema: RJSFSchema = {
76+
type: 'object',
77+
properties: {
7878
experimental_defaultFormStateBehavior: {
7979
title: 'Default Form State Behavior (Experimental)',
8080
type: 'object',
@@ -157,11 +157,37 @@ const liveSettingsSelectSchema: RJSFSchema = {
157157
},
158158
],
159159
},
160+
mergeDefaultsIntoFormData: {
161+
type: 'string',
162+
title: 'Merge defaults into formData',
163+
default: 'useFormDataIfPresent',
164+
oneOf: [
165+
{
166+
type: 'string',
167+
title: 'Use undefined field value if present',
168+
enum: ['useFormDataIfPresent'],
169+
},
170+
{
171+
type: 'string',
172+
title: 'Use default for undefined field value',
173+
enum: ['useDefaultIfFormDataUndefined'],
174+
},
175+
],
176+
},
160177
},
161178
},
162179
},
163180
};
164181

182+
const liveSettingsBooleanUiSchema: UiSchema = {
183+
showErrorList: {
184+
'ui:widget': 'radio',
185+
'ui:options': {
186+
inline: true,
187+
},
188+
},
189+
};
190+
165191
const liveSettingsSelectUiSchema: UiSchema = {
166192
experimental_defaultFormStateBehavior: {
167193
'ui:options': {
@@ -282,6 +308,7 @@ export default function Header({
282308
formData={liveSettings}
283309
validator={localValidator}
284310
onChange={handleSetLiveSettings}
311+
uiSchema={liveSettingsBooleanUiSchema}
285312
>
286313
<div />
287314
</Form>

packages/utils/src/mergeDefaultsWithFormData.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,31 @@ import { GenericObjectType } from '../src';
1212
* are deeply merged; additional entries from the defaults are ignored unless `mergeExtraArrayDefaults` is true, in
1313
* which case the extras are appended onto the end of the form data
1414
* - when the array is not set in form data, the default is copied over
15-
* - scalars are overwritten/set by form data
15+
* - scalars are overwritten/set by form data unless undefined and there is a default AND `defaultSupercedesUndefined`
16+
* is true
1617
*
1718
* @param [defaults] - The defaults to merge
1819
* @param [formData] - The form data into which the defaults will be merged
1920
* @param [mergeExtraArrayDefaults=false] - If true, any additional default array entries are appended onto the formData
21+
* @param [defaultSupercedesUndefined=false] - If true, an explicit undefined value will be overwritten by the default value
2022
* @returns - The resulting merged form data with defaults
2123
*/
2224
export default function mergeDefaultsWithFormData<T = any>(
2325
defaults?: T,
2426
formData?: T,
25-
mergeExtraArrayDefaults = false
27+
mergeExtraArrayDefaults = false,
28+
defaultSupercedesUndefined = false
2629
): T | undefined {
2730
if (Array.isArray(formData)) {
2831
const defaultsArray = Array.isArray(defaults) ? defaults : [];
2932
const mapped = formData.map((value, idx) => {
3033
if (defaultsArray[idx]) {
31-
return mergeDefaultsWithFormData<any>(defaultsArray[idx], value, mergeExtraArrayDefaults);
34+
return mergeDefaultsWithFormData<any>(
35+
defaultsArray[idx],
36+
value,
37+
mergeExtraArrayDefaults,
38+
defaultSupercedesUndefined
39+
);
3240
}
3341
return value;
3442
});
@@ -44,10 +52,14 @@ export default function mergeDefaultsWithFormData<T = any>(
4452
acc[key as keyof T] = mergeDefaultsWithFormData<T>(
4553
defaults ? get(defaults, key) : {},
4654
get(formData, key),
47-
mergeExtraArrayDefaults
55+
mergeExtraArrayDefaults,
56+
defaultSupercedesUndefined
4857
);
4958
return acc;
5059
}, acc);
5160
}
61+
if (defaultSupercedesUndefined && formData === undefined) {
62+
return defaults;
63+
}
5264
return formData;
5365
}

packages/utils/src/schema/getClosestMatchingOption.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export function calculateIndexScore<T = any, S extends StrictRJSFSchema = RJSFSc
5151
validator: ValidatorType<T, S, F>,
5252
rootSchema: S,
5353
schema?: S,
54-
formData: any = {}
54+
formData?: any
5555
): number {
5656
let totalScore = 0;
5757
if (schema) {
@@ -83,7 +83,11 @@ export function calculateIndexScore<T = any, S extends StrictRJSFSchema = RJSFSc
8383
);
8484
}
8585
if (value.type === 'object') {
86-
return score + calculateIndexScore<T, S, F>(validator, rootSchema, value as S, formValue || {});
86+
if (isObject(formValue)) {
87+
// If the structure is matching then give it a little boost in score
88+
score += 1;
89+
}
90+
return score + calculateIndexScore<T, S, F>(validator, rootSchema, value as S, formValue);
8791
}
8892
if (value.type === guessType(formValue)) {
8993
// If the types match, then we bump the score by one

packages/utils/src/schema/getDefaultFormState.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -586,12 +586,14 @@ export default function getDefaultFormState<
586586
// No form data? Use schema defaults.
587587
return defaults;
588588
}
589-
const { mergeExtraDefaults } = experimental_defaultFormStateBehavior?.arrayMinItems || {};
589+
const { mergeDefaultsIntoFormData, arrayMinItems = {} } = experimental_defaultFormStateBehavior || {};
590+
const { mergeExtraDefaults } = arrayMinItems;
591+
const defaultSupercedesUndefined = mergeDefaultsIntoFormData === 'useDefaultIfFormDataUndefined';
590592
if (isObject(formData)) {
591-
return mergeDefaultsWithFormData<T>(defaults as T, formData, mergeExtraDefaults);
593+
return mergeDefaultsWithFormData<T>(defaults as T, formData, mergeExtraDefaults, defaultSupercedesUndefined);
592594
}
593595
if (Array.isArray(formData)) {
594-
return mergeDefaultsWithFormData<T[]>(defaults as T[], formData, mergeExtraDefaults);
596+
return mergeDefaultsWithFormData<T[]>(defaults as T[], formData, mergeExtraDefaults, defaultSupercedesUndefined);
595597
}
596598
return formData;
597599
}

0 commit comments

Comments
 (0)