@@ -30,8 +30,12 @@ import {
30
30
ValidatorType ,
31
31
} from '../types' ;
32
32
import isMultiSelect from './isMultiSelect' ;
33
+ import isSelect from './isSelect' ;
33
34
import retrieveSchema , { resolveDependencies } from './retrieveSchema' ;
35
+ import isConstant from '../isConstant' ;
34
36
import { JSONSchema7Object } from 'json-schema' ;
37
+ import isEqual from 'lodash/isEqual' ;
38
+ import optionsList from '../optionsList' ;
35
39
36
40
const PRIMITIVE_TYPES = [ 'string' , 'number' , 'integer' , 'boolean' , 'null' ] ;
37
41
@@ -170,6 +174,10 @@ interface ComputeDefaultsProps<T = any, S extends StrictRJSFSchema = RJSFSchema>
170
174
experimental_customMergeAllOf ?: Experimental_CustomMergeAllOf < S > ;
171
175
/** Optional flag, if true, indicates this schema was required in the parent schema. */
172
176
required ?: boolean ;
177
+ /** Optional flag, if true, It will merge defaults into formData.
178
+ * The formData should take precedence unless it's not valid. This is useful when for example the value from formData does not exist in the schema 'enum' property, in such cases we take the value from the defaults because the value from the formData is not valid.
179
+ */
180
+ shouldMergeDefaultsIntoFormData ?: boolean ;
173
181
}
174
182
175
183
/** Computes the defaults for the current `schema` given the `rawFormData` and `parentDefaults` if any. This drills into
@@ -194,6 +202,7 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
194
202
experimental_defaultFormStateBehavior = undefined ,
195
203
experimental_customMergeAllOf = undefined ,
196
204
required,
205
+ shouldMergeDefaultsIntoFormData = false ,
197
206
} = computeDefaultsProps ;
198
207
const formData : T = ( isObject ( rawFormData ) ? rawFormData : { } ) as T ;
199
208
const schema : S = isObject ( rawSchema ) ? rawSchema : ( { } as S ) ;
@@ -246,6 +255,7 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
246
255
parentDefaults : Array . isArray ( parentDefaults ) ? parentDefaults [ idx ] : undefined ,
247
256
rawFormData : formData as T ,
248
257
required,
258
+ shouldMergeDefaultsIntoFormData,
249
259
} )
250
260
) as T [ ] ;
251
261
} else if ( ONE_OF_KEY in schema ) {
@@ -267,7 +277,7 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
267
277
getClosestMatchingOption < T , S , F > (
268
278
validator ,
269
279
rootSchema ,
270
- isEmpty ( formData ) ? undefined : formData ,
280
+ rawFormData ,
271
281
oneOf as S [ ] ,
272
282
0 ,
273
283
discriminator ,
@@ -285,7 +295,7 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
285
295
getClosestMatchingOption < T , S , F > (
286
296
validator ,
287
297
rootSchema ,
288
- isEmpty ( formData ) ? undefined : formData ,
298
+ rawFormData ,
289
299
anyOf as S [ ] ,
290
300
0 ,
291
301
discriminator ,
@@ -305,6 +315,7 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
305
315
parentDefaults : defaults as T | undefined ,
306
316
rawFormData : formData as T ,
307
317
required,
318
+ shouldMergeDefaultsIntoFormData,
308
319
} ) ;
309
320
}
310
321
@@ -315,7 +326,68 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
315
326
316
327
const defaultBasedOnSchemaType = getDefaultBasedOnSchemaType ( validator , schema , computeDefaultsProps , defaults ) ;
317
328
318
- return defaultBasedOnSchemaType ?? defaults ;
329
+ let defaultsWithFormData = defaultBasedOnSchemaType ?? defaults ;
330
+ // if shouldMergeDefaultsIntoFormData is true, then merge the defaults into the formData.
331
+ if ( shouldMergeDefaultsIntoFormData ) {
332
+ const { arrayMinItems = { } } = experimental_defaultFormStateBehavior || { } ;
333
+ const { mergeExtraDefaults } = arrayMinItems ;
334
+
335
+ const matchingFormData = ensureFormDataMatchingSchema (
336
+ validator ,
337
+ schema ,
338
+ rootSchema ,
339
+ rawFormData ,
340
+ experimental_defaultFormStateBehavior
341
+ ) ;
342
+ if ( ! isObject ( rawFormData ) ) {
343
+ defaultsWithFormData = mergeDefaultsWithFormData < T > (
344
+ defaultsWithFormData as T ,
345
+ matchingFormData as T ,
346
+ mergeExtraDefaults ,
347
+ true
348
+ ) as T ;
349
+ }
350
+ }
351
+
352
+ return defaultsWithFormData ;
353
+ }
354
+
355
+ /**
356
+ * Ensure that the formData matches the given schema. If it's not matching in the case of a selectField, we change it to match the schema.
357
+ *
358
+ * @param validator - an implementation of the `ValidatorType` interface that will be used when necessary
359
+ * @param schema - The schema for which the formData state is desired
360
+ * @param rootSchema - The root schema, used to primarily to look up `$ref`s
361
+ * @param formData - The current formData
362
+ * @param experimental_defaultFormStateBehavior - Optional configuration object, if provided, allows users to override default form state behavior
363
+ * @returns - valid formData that matches schema
364
+ */
365
+ export function ensureFormDataMatchingSchema <
366
+ T = any ,
367
+ S extends StrictRJSFSchema = RJSFSchema ,
368
+ F extends FormContextType = any
369
+ > (
370
+ validator : ValidatorType < T , S , F > ,
371
+ schema : S ,
372
+ rootSchema : S ,
373
+ formData : T | undefined ,
374
+ experimental_defaultFormStateBehavior ?: Experimental_DefaultFormStateBehavior
375
+ ) : T | T [ ] | undefined {
376
+ const isSelectField = ! isConstant ( schema ) && isSelect ( validator , schema , rootSchema ) ;
377
+ let validFormData : T | T [ ] | undefined = formData ;
378
+ if ( isSelectField ) {
379
+ const getOptionsList = optionsList ( schema ) ;
380
+ const isValid = getOptionsList ?. some ( ( option ) => isEqual ( option . value , formData ) ) ;
381
+ validFormData = isValid ? formData : undefined ;
382
+ }
383
+
384
+ // Override the formData with the const if the constAsDefaults is set to always
385
+ const constTakesPrecedence = schema [ CONST_KEY ] && experimental_defaultFormStateBehavior ?. constAsDefaults === 'always' ;
386
+ if ( constTakesPrecedence ) {
387
+ validFormData = schema . const as T ;
388
+ }
389
+
390
+ return validFormData ;
319
391
}
320
392
321
393
/** Computes the default value for objects.
@@ -337,6 +409,7 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
337
409
experimental_defaultFormStateBehavior = undefined ,
338
410
experimental_customMergeAllOf = undefined ,
339
411
required,
412
+ shouldMergeDefaultsIntoFormData,
340
413
} : ComputeDefaultsProps < T , S > = { } ,
341
414
defaults ?: T | T [ ] | undefined
342
415
) : T {
@@ -370,6 +443,7 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
370
443
parentDefaults : get ( defaults , [ key ] ) ,
371
444
rawFormData : get ( formData , [ key ] ) ,
372
445
required : retrievedSchema . required ?. includes ( key ) ,
446
+ shouldMergeDefaultsIntoFormData,
373
447
} ) ;
374
448
maybeAddDefaultToObject < T > (
375
449
acc ,
@@ -414,6 +488,7 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
414
488
parentDefaults : get ( defaults , [ key ] ) ,
415
489
rawFormData : get ( formData , [ key ] ) ,
416
490
required : retrievedSchema . required ?. includes ( key ) ,
491
+ shouldMergeDefaultsIntoFormData,
417
492
} ) ;
418
493
// Since these are additional properties we don't need to add the `experimental_defaultFormStateBehavior` prop
419
494
maybeAddDefaultToObject < T > (
@@ -448,6 +523,7 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
448
523
experimental_defaultFormStateBehavior = undefined ,
449
524
experimental_customMergeAllOf = undefined ,
450
525
required,
526
+ shouldMergeDefaultsIntoFormData,
451
527
} : ComputeDefaultsProps < T , S > = { } ,
452
528
defaults ?: T | T [ ] | undefined
453
529
) : T | T [ ] | undefined {
@@ -475,6 +551,7 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
475
551
experimental_customMergeAllOf,
476
552
parentDefaults : item ,
477
553
required,
554
+ shouldMergeDefaultsIntoFormData,
478
555
} ) ;
479
556
} ) as T [ ] ;
480
557
}
@@ -494,6 +571,7 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
494
571
rawFormData : item ,
495
572
parentDefaults : get ( defaults , [ idx ] ) ,
496
573
required,
574
+ shouldMergeDefaultsIntoFormData,
497
575
} ) ;
498
576
} ) as T [ ] ;
499
577
@@ -542,6 +620,7 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
542
620
experimental_defaultFormStateBehavior,
543
621
experimental_customMergeAllOf,
544
622
required,
623
+ shouldMergeDefaultsIntoFormData,
545
624
} )
546
625
) as T [ ] ;
547
626
// then fill up the rest with either the item default or empty, up to minItems
@@ -608,26 +687,33 @@ export default function getDefaultFormState<
608
687
throw new Error ( 'Invalid schema: ' + theSchema ) ;
609
688
}
610
689
const schema = retrieveSchema < T , S , F > ( validator , theSchema , rootSchema , formData , experimental_customMergeAllOf ) ;
690
+
691
+ // Get the computed defaults with 'shouldMergeDefaultsIntoFormData' set to true to merge defaults into formData.
692
+ // This is done when for example the value from formData does not exist in the schema 'enum' property, in such
693
+ // cases we take the value from the defaults because the value from the formData is not valid.
611
694
const defaults = computeDefaults < T , S , F > ( validator , schema , {
612
695
rootSchema,
613
696
includeUndefinedValues,
614
697
experimental_defaultFormStateBehavior,
615
698
experimental_customMergeAllOf,
616
699
rawFormData : formData ,
700
+ shouldMergeDefaultsIntoFormData : true ,
617
701
} ) ;
618
702
619
- if ( formData === undefined || formData === null || ( typeof formData === 'number' && isNaN ( formData ) ) ) {
620
- // No form data? Use schema defaults.
621
- return defaults ;
622
- }
623
- const { mergeDefaultsIntoFormData, arrayMinItems = { } } = experimental_defaultFormStateBehavior || { } ;
624
- const { mergeExtraDefaults } = arrayMinItems ;
625
- const defaultSupercedesUndefined = mergeDefaultsIntoFormData === 'useDefaultIfFormDataUndefined' ;
626
- if ( isObject ( formData ) ) {
627
- return mergeDefaultsWithFormData < T > ( defaults as T , formData , mergeExtraDefaults , defaultSupercedesUndefined ) ;
628
- }
629
- if ( Array . isArray ( formData ) ) {
630
- return mergeDefaultsWithFormData < T [ ] > ( defaults as T [ ] , formData , mergeExtraDefaults , defaultSupercedesUndefined ) ;
703
+ // If the formData is an object or an array, add additional properties from formData and override formData with
704
+ // defaults since the defaults are already merged with formData.
705
+ if ( isObject ( formData ) || Array . isArray ( formData ) ) {
706
+ const { mergeDefaultsIntoFormData } = experimental_defaultFormStateBehavior || { } ;
707
+ const defaultSupercedesUndefined = mergeDefaultsIntoFormData === 'useDefaultIfFormDataUndefined' ;
708
+ const result = mergeDefaultsWithFormData < T | T [ ] > (
709
+ defaults ,
710
+ formData ,
711
+ true , // set to true to add any additional default array entries.
712
+ defaultSupercedesUndefined ,
713
+ true // set to true to override formData with defaults if they exist.
714
+ ) ;
715
+ return result ;
631
716
}
632
- return formData ;
717
+
718
+ return defaults ;
633
719
}
0 commit comments