Skip to content

Commit 3591e80

Browse files
committed
feat: allow default value overrides form data
1 parent 4337166 commit 3591e80

File tree

7 files changed

+40
-24
lines changed

7 files changed

+40
-24
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ should change the heading of the (upcoming) version to include a major version b
1616
1717
-->
1818

19+
# 6.0.0-beta.9
20+
1921
## @rjsf/util
2022

2123
- Allow form value overrides with defaults [#4625](https://github.com/rjsf-team/react-jsonschema-form/pull/4625

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ NOTE: If there is a default for a field and the `formData` is unspecified, the d
269269
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
270270
| `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` |
271271
| `useDefaultIfFormDataUndefined` | If the value of a field within the `formData` is `undefined`, then use the default value instead |
272-
| `useDefault` | Always use the default value instead of form data |
272+
| `useDefaultAlways` | Always use the default value instead of form data |
273273

274274
|
275275

packages/playground/src/components/Header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ const liveSettingsSelectSchema: RJSFSchema = {
204204
{
205205
type: 'string',
206206
title: 'Always use default for field value',
207-
enum: ['useDefault'],
207+
enum: ['useDefaultAlways'],
208208
},
209209
],
210210
},

packages/utils/src/mergeDefaultsWithFormData.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import get from 'lodash/get';
22

33
import isObject from './isObject';
4-
import { GenericObjectType } from '../src';
4+
import { GenericObjectType, OverrideFormDataStrategy } from '../src';
55
import isNil from 'lodash/isNil';
66

77
/** Merges the `defaults` object of type `T` into the `formData` of type `T`
@@ -20,25 +20,24 @@ import isNil from 'lodash/isNil';
2020
* @param [formData] - The form data into which the defaults will be merged
2121
* @param [mergeExtraArrayDefaults=false] - If true, any additional default array entries are appended onto the formData
2222
* @param [defaultSupercedesUndefined=false] - If true, an explicit undefined value will be overwritten by the default value
23-
* @param [overrideFormDataWithDefaultsStrategy='noop'] - If not 'noop', the default value will overwrite the form data value. Values can be either replaced or merged, if the value
24-
* doesn't exist in the default, we take it from formData and in the case where the value is set to undefined in formData.
25-
* This is useful when we have already merged formData with defaults and want to add an additional field from formData
26-
* that does not exist in defaults.
23+
* @param [overrideFormDataWithDefaultsStrategy=OverrideFormDataStrategy.noop] - Strategy for merging defaults and form data
2724
* @returns - The resulting merged form data with defaults
2825
*/
2926
export default function mergeDefaultsWithFormData<T = any>(
3027
defaults?: T,
3128
formData?: T,
3229
mergeExtraArrayDefaults = false,
3330
defaultSupercedesUndefined = false,
34-
overrideFormDataWithDefaultsStrategy: 'noop' | 'replace' | 'merge' = 'noop',
31+
overrideFormDataWithDefaultsStrategy: OverrideFormDataStrategy = OverrideFormDataStrategy.noop,
3532
): T | undefined {
3633
if (Array.isArray(formData)) {
3734
const defaultsArray = Array.isArray(defaults) ? defaults : [];
3835

3936
// If overrideFormDataWithDefaultsStrategy is not noop, we want to override the formData with the defaults
40-
const overrideArray = overrideFormDataWithDefaultsStrategy !== 'noop' ? defaultsArray : formData;
41-
const overrideOppositeArray = overrideFormDataWithDefaultsStrategy !== 'noop' ? formData : defaultsArray;
37+
const overrideArray =
38+
overrideFormDataWithDefaultsStrategy !== OverrideFormDataStrategy.noop ? defaultsArray : formData;
39+
const overrideOppositeArray =
40+
overrideFormDataWithDefaultsStrategy !== OverrideFormDataStrategy.noop ? formData : defaultsArray;
4241

4342
const mapped = overrideArray.map((value, idx) => {
4443
// We want to explicitly make sure that the value is NOT undefined since null, 0 and empty space are valid values
@@ -55,17 +54,18 @@ export default function mergeDefaultsWithFormData<T = any>(
5554
});
5655

5756
// Merge any extra defaults when mergeExtraArrayDefaults is true
58-
// Or when overrideFormDataWithDefaults is not noop and the default array is shorter than the formData array
57+
// Or when overrideFormDataWithDefaults is 'merge' and the default array is shorter than the formData array
5958
if (
60-
(mergeExtraArrayDefaults || overrideFormDataWithDefaultsStrategy === 'merge') &&
59+
(mergeExtraArrayDefaults || overrideFormDataWithDefaultsStrategy === OverrideFormDataStrategy.merge) &&
6160
mapped.length < overrideOppositeArray.length
6261
) {
6362
mapped.push(...overrideOppositeArray.slice(mapped.length));
6463
}
6564
return mapped as unknown as T;
6665
}
6766
if (isObject(formData)) {
68-
const iterationSource = overrideFormDataWithDefaultsStrategy === 'replace' ? (defaults ?? {}) : formData;
67+
const iterationSource =
68+
overrideFormDataWithDefaultsStrategy === OverrideFormDataStrategy.replace ? (defaults ?? {}) : formData;
6969
const acc: { [key in keyof T]: any } = Object.assign({}, defaults); // Prevent mutation of source object.
7070
return Object.keys(iterationSource as GenericObjectType).reduce((acc, key) => {
7171
const keyValue = get(formData, key);
@@ -74,11 +74,11 @@ export default function mergeDefaultsWithFormData<T = any>(
7474
// overrideFormDataWithDefaultsStrategy can be 'merge' only when the key value exists in defaults
7575
// Or if the key value doesn't exist in formData
7676
const keyOverrideDefaultStrategy =
77-
overrideFormDataWithDefaultsStrategy === 'replace'
78-
? 'replace'
77+
overrideFormDataWithDefaultsStrategy === OverrideFormDataStrategy.replace
78+
? OverrideFormDataStrategy.replace
7979
: keyExistsInDefaults || !keyExistsInFormData
8080
? overrideFormDataWithDefaultsStrategy
81-
: 'noop';
81+
: OverrideFormDataStrategy.noop;
8282
acc[key as keyof T] = mergeDefaultsWithFormData<T>(
8383
defaults ? get(defaults, key) : {},
8484
keyValue,
@@ -99,10 +99,10 @@ export default function mergeDefaultsWithFormData<T = any>(
9999
if (
100100
(defaultSupercedesUndefined &&
101101
((!isNil(defaults) && isNil(formData)) || (typeof formData === 'number' && isNaN(formData)))) ||
102-
(overrideFormDataWithDefaultsStrategy === 'merge' && !isNil(formData))
102+
(overrideFormDataWithDefaultsStrategy === OverrideFormDataStrategy.merge && !isNil(formData))
103103
) {
104104
return defaults;
105105
}
106106

107-
return overrideFormDataWithDefaultsStrategy === 'replace' ? defaults : formData;
107+
return overrideFormDataWithDefaultsStrategy === OverrideFormDataStrategy.replace ? defaults : formData;
108108
}

packages/utils/src/schema/getDefaultFormState.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
Experimental_DefaultFormStateBehavior,
2727
FormContextType,
2828
GenericObjectType,
29+
OverrideFormDataStrategy,
2930
RJSFSchema,
3031
StrictRJSFSchema,
3132
ValidatorType,
@@ -363,7 +364,9 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
363364
matchingFormData as T,
364365
mergeExtraDefaults,
365366
true,
366-
experimental_defaultFormStateBehavior?.mergeDefaultsIntoFormData === 'useDefault' ? 'replace' : 'noop',
367+
experimental_defaultFormStateBehavior?.mergeDefaultsIntoFormData === 'useDefaultAlways'
368+
? OverrideFormDataStrategy.replace
369+
: OverrideFormDataStrategy.noop,
367370
) as T;
368371
}
369372
}
@@ -733,7 +736,7 @@ export default function getDefaultFormState<
733736
formData,
734737
true, // set to true to add any additional default array entries.
735738
defaultSupercedesUndefined,
736-
'merge', // set to 'merge' to override formData with defaults if they exist.
739+
OverrideFormDataStrategy.merge, // set to 'merge' to override formData with defaults if they exist.
737740
);
738741
return result;
739742
}

packages/utils/src/types.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,9 @@ export type Experimental_DefaultFormStateBehavior = {
9898
* even if that value is explicitly set to `undefined`
9999
* - `useDefaultIfFormDataUndefined`: - If the value of a field within the `formData` is `undefined`, then use the
100100
* default value instead
101-
* - `useDefault`: - Always use the default value
101+
* - `useDefaultAlways`: - Always use the default value
102102
*/
103-
mergeDefaultsIntoFormData?: 'useFormDataIfPresent' | 'useDefaultIfFormDataUndefined' | 'useDefault';
103+
mergeDefaultsIntoFormData?: 'useFormDataIfPresent' | 'useDefaultIfFormDataUndefined' | 'useDefaultAlways';
104104
/** Optional enumerated flag controlling how const values are merged into the form data as defaults when dealing with
105105
* undefined values, defaulting to `always`. The defaulting behavior for this flag will always be controlled by the
106106
* `emptyObjectField` flag value. For instance, if `populateRequiredDefaults` is set and the const value is not
@@ -1266,3 +1266,14 @@ export interface SchemaUtilsType<T = any, S extends StrictRJSFSchema = RJSFSchem
12661266
*/
12671267
toPathSchema(schema: S, name?: string, formData?: T): PathSchema<T>;
12681268
}
1269+
1270+
/** Strategy for merging defaults with existing form data */
1271+
export enum OverrideFormDataStrategy {
1272+
/** No merge or override applied */
1273+
noop,
1274+
/** If the value doesn't exist in the default, we take it from formData and in the case where the value is set to undefined in formData.
1275+
* This is useful when we have already merged formData with defaults and want to add an additional field from formData that does not exist in defaults */
1276+
merge,
1277+
/** Replace form data with defined default */
1278+
replace,
1279+
}

packages/utils/test/schema/getDefaultFormStateTest.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1341,9 +1341,9 @@ export default function getDefaultFormStateTest(testValidator: TestValidatorType
13411341
});
13421342
});
13431343

1344-
describe('mergeDefaultsIntoFormData set to "useDefault"', () => {
1344+
describe('mergeDefaultsIntoFormData set to "useDefaultAlways"', () => {
13451345
const experimental_defaultFormStateBehavior: Experimental_DefaultFormStateBehavior = {
1346-
mergeDefaultsIntoFormData: 'useDefault',
1346+
mergeDefaultsIntoFormData: 'useDefaultAlways',
13471347
};
13481348

13491349
test('getDefaultFormState', () => {

0 commit comments

Comments
 (0)