@@ -30,8 +30,12 @@ import {
3030 ValidatorType ,
3131} from '../types' ;
3232import isMultiSelect from './isMultiSelect' ;
33+ import isSelect from './isSelect' ;
3334import retrieveSchema , { resolveDependencies } from './retrieveSchema' ;
35+ import isConstant from '../isConstant' ;
3436import { JSONSchema7Object } from 'json-schema' ;
37+ import isEqual from 'lodash/isEqual' ;
38+ import optionsList from '../optionsList' ;
3539
3640const PRIMITIVE_TYPES = [ 'string' , 'number' , 'integer' , 'boolean' , 'null' ] ;
3741
@@ -170,6 +174,10 @@ interface ComputeDefaultsProps<T = any, S extends StrictRJSFSchema = RJSFSchema>
170174 experimental_customMergeAllOf ?: Experimental_CustomMergeAllOf < S > ;
171175 /** Optional flag, if true, indicates this schema was required in the parent schema. */
172176 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 ;
173181}
174182
175183/** 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
194202 experimental_defaultFormStateBehavior = undefined ,
195203 experimental_customMergeAllOf = undefined ,
196204 required,
205+ shouldMergeDefaultsIntoFormData = false ,
197206 } = computeDefaultsProps ;
198207 const formData : T = ( isObject ( rawFormData ) ? rawFormData : { } ) as T ;
199208 const schema : S = isObject ( rawSchema ) ? rawSchema : ( { } as S ) ;
@@ -246,6 +255,7 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
246255 parentDefaults : Array . isArray ( parentDefaults ) ? parentDefaults [ idx ] : undefined ,
247256 rawFormData : formData as T ,
248257 required,
258+ shouldMergeDefaultsIntoFormData,
249259 } )
250260 ) as T [ ] ;
251261 } else if ( ONE_OF_KEY in schema ) {
@@ -267,7 +277,7 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
267277 getClosestMatchingOption < T , S , F > (
268278 validator ,
269279 rootSchema ,
270- isEmpty ( formData ) ? undefined : formData ,
280+ rawFormData ,
271281 oneOf as S [ ] ,
272282 0 ,
273283 discriminator ,
@@ -285,7 +295,7 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
285295 getClosestMatchingOption < T , S , F > (
286296 validator ,
287297 rootSchema ,
288- isEmpty ( formData ) ? undefined : formData ,
298+ rawFormData ,
289299 anyOf as S [ ] ,
290300 0 ,
291301 discriminator ,
@@ -305,6 +315,7 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
305315 parentDefaults : defaults as T | undefined ,
306316 rawFormData : formData as T ,
307317 required,
318+ shouldMergeDefaultsIntoFormData,
308319 } ) ;
309320 }
310321
@@ -315,7 +326,68 @@ export function computeDefaults<T = any, S extends StrictRJSFSchema = RJSFSchema
315326
316327 const defaultBasedOnSchemaType = getDefaultBasedOnSchemaType ( validator , schema , computeDefaultsProps , defaults ) ;
317328
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 ;
319391}
320392
321393/** Computes the default value for objects.
@@ -337,6 +409,7 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
337409 experimental_defaultFormStateBehavior = undefined ,
338410 experimental_customMergeAllOf = undefined ,
339411 required,
412+ shouldMergeDefaultsIntoFormData,
340413 } : ComputeDefaultsProps < T , S > = { } ,
341414 defaults ?: T | T [ ] | undefined
342415) : T {
@@ -370,6 +443,7 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
370443 parentDefaults : get ( defaults , [ key ] ) ,
371444 rawFormData : get ( formData , [ key ] ) ,
372445 required : retrievedSchema . required ?. includes ( key ) ,
446+ shouldMergeDefaultsIntoFormData,
373447 } ) ;
374448 maybeAddDefaultToObject < T > (
375449 acc ,
@@ -414,6 +488,7 @@ export function getObjectDefaults<T = any, S extends StrictRJSFSchema = RJSFSche
414488 parentDefaults : get ( defaults , [ key ] ) ,
415489 rawFormData : get ( formData , [ key ] ) ,
416490 required : retrievedSchema . required ?. includes ( key ) ,
491+ shouldMergeDefaultsIntoFormData,
417492 } ) ;
418493 // Since these are additional properties we don't need to add the `experimental_defaultFormStateBehavior` prop
419494 maybeAddDefaultToObject < T > (
@@ -448,6 +523,7 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
448523 experimental_defaultFormStateBehavior = undefined ,
449524 experimental_customMergeAllOf = undefined ,
450525 required,
526+ shouldMergeDefaultsIntoFormData,
451527 } : ComputeDefaultsProps < T , S > = { } ,
452528 defaults ?: T | T [ ] | undefined
453529) : T | T [ ] | undefined {
@@ -475,6 +551,7 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
475551 experimental_customMergeAllOf,
476552 parentDefaults : item ,
477553 required,
554+ shouldMergeDefaultsIntoFormData,
478555 } ) ;
479556 } ) as T [ ] ;
480557 }
@@ -494,6 +571,7 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
494571 rawFormData : item ,
495572 parentDefaults : get ( defaults , [ idx ] ) ,
496573 required,
574+ shouldMergeDefaultsIntoFormData,
497575 } ) ;
498576 } ) as T [ ] ;
499577
@@ -542,6 +620,7 @@ export function getArrayDefaults<T = any, S extends StrictRJSFSchema = RJSFSchem
542620 experimental_defaultFormStateBehavior,
543621 experimental_customMergeAllOf,
544622 required,
623+ shouldMergeDefaultsIntoFormData,
545624 } )
546625 ) as T [ ] ;
547626 // then fill up the rest with either the item default or empty, up to minItems
@@ -608,26 +687,33 @@ export default function getDefaultFormState<
608687 throw new Error ( 'Invalid schema: ' + theSchema ) ;
609688 }
610689 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.
611694 const defaults = computeDefaults < T , S , F > ( validator , schema , {
612695 rootSchema,
613696 includeUndefinedValues,
614697 experimental_defaultFormStateBehavior,
615698 experimental_customMergeAllOf,
616699 rawFormData : formData ,
700+ shouldMergeDefaultsIntoFormData : true ,
617701 } ) ;
618702
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 ;
631716 }
632- return formData ;
717+
718+ return defaults ;
633719}
0 commit comments