@@ -46,6 +46,7 @@ import {
4646 isFormSubmitEvent ,
4747 debounceAsync ,
4848 isEmptyContainer ,
49+ withLatest ,
4950} from './utils' ;
5051import { FormContextKey } from './symbols' ;
5152import { validateYupSchema , validateObjectSchema } from './validate' ;
@@ -156,6 +157,70 @@ export function useForm<TValues extends Record<string, any> = Record<string, any
156157 const meta = useFormMeta ( fieldsByPath , formValues , originalInitialValues , errors ) ;
157158
158159 const schema = opts ?. validationSchema ;
160+
161+ /**
162+ * Batches validation runs in 5ms batches
163+ * Must have two distinct batch queues to make sure they don't override each other settings #3783
164+ */
165+ const debouncedSilentValidation = debounceAsync ( _validateSchema , 5 ) ;
166+ const debouncedValidation = debounceAsync ( _validateSchema , 5 ) ;
167+
168+ const validateSchema = withLatest (
169+ async ( mode : SchemaValidationMode ) => {
170+ return ( await mode ) === 'silent' ? debouncedSilentValidation ( ) : debouncedValidation ( ) ;
171+ } ,
172+ ( formResult , [ mode ] ) => {
173+ // fields by id lookup
174+ const fieldsById = formCtx . fieldsByPath . value || { } ;
175+ // errors fields names, we need it to also check if custom errors are updated
176+ const currentErrorsPaths = keysOf ( formCtx . errorBag . value ) ;
177+ // collect all the keys from the schema and all fields
178+ // this ensures we have a complete keymap of all the fields
179+ const paths = [
180+ ...new Set ( [ ...keysOf ( formResult . results ) , ...keysOf ( fieldsById ) , ...currentErrorsPaths ] ) ,
181+ ] as string [ ] ;
182+
183+ // aggregates the paths into a single result object while applying the results on the fields
184+ return paths . reduce (
185+ ( validation , path ) => {
186+ const field = fieldsById [ path ] ;
187+ const messages = ( formResult . results [ path ] || { errors : [ ] as string [ ] } ) . errors ;
188+ const fieldResult = {
189+ errors : messages ,
190+ valid : ! messages . length ,
191+ } ;
192+ validation . results [ path as keyof TValues ] = fieldResult ;
193+ if ( ! fieldResult . valid ) {
194+ validation . errors [ path as keyof TValues ] = fieldResult . errors [ 0 ] ;
195+ }
196+
197+ // field not rendered
198+ if ( ! field ) {
199+ setFieldError ( path , messages ) ;
200+
201+ return validation ;
202+ }
203+
204+ // always update the valid flag regardless of the mode
205+ applyFieldMutation ( field , f => ( f . meta . valid = fieldResult . valid ) ) ;
206+ if ( mode === 'silent' ) {
207+ return validation ;
208+ }
209+
210+ const wasValidated = Array . isArray ( field ) ? field . some ( f => f . meta . validated ) : field . meta . validated ;
211+ if ( mode === 'validated-only' && ! wasValidated ) {
212+ return validation ;
213+ }
214+
215+ applyFieldMutation ( field , f => f . setState ( { errors : fieldResult . errors } ) ) ;
216+
217+ return validation ;
218+ } ,
219+ { valid : formResult . valid , results : { } , errors : { } } as FormValidationResult < TValues >
220+ ) ;
221+ }
222+ ) ;
223+
159224 const formCtx : PrivateFormContext < TValues > = {
160225 formId,
161226 fieldsByPath,
@@ -660,66 +725,6 @@ export function useForm<TValues extends Record<string, any> = Record<string, any
660725 return formResult ;
661726 }
662727
663- /**
664- * Batches validation runs in 5ms batches
665- * Must have two distinct batch queues to make sure they don't override each other settings #3783
666- */
667- const debouncedSilentValidation = debounceAsync ( _validateSchema , 5 ) ;
668- const debouncedValidation = debounceAsync ( _validateSchema , 5 ) ;
669-
670- async function validateSchema ( mode : SchemaValidationMode ) : Promise < FormValidationResult < TValues > > {
671- const formResult = await ( mode === 'silent' ? debouncedSilentValidation ( ) : debouncedValidation ( ) ) ;
672-
673- // fields by id lookup
674- const fieldsById = formCtx . fieldsByPath . value || { } ;
675- // errors fields names, we need it to also check if custom errors are updated
676- const currentErrorsPaths = keysOf ( formCtx . errorBag . value ) ;
677- // collect all the keys from the schema and all fields
678- // this ensures we have a complete keymap of all the fields
679- const paths = [
680- ...new Set ( [ ...keysOf ( formResult . results ) , ...keysOf ( fieldsById ) , ...currentErrorsPaths ] ) ,
681- ] as string [ ] ;
682-
683- // aggregates the paths into a single result object while applying the results on the fields
684- return paths . reduce (
685- ( validation , path ) => {
686- const field = fieldsById [ path ] ;
687- const messages = ( formResult . results [ path ] || { errors : [ ] as string [ ] } ) . errors ;
688- const fieldResult = {
689- errors : messages ,
690- valid : ! messages . length ,
691- } ;
692- validation . results [ path as keyof TValues ] = fieldResult ;
693- if ( ! fieldResult . valid ) {
694- validation . errors [ path as keyof TValues ] = fieldResult . errors [ 0 ] ;
695- }
696-
697- // field not rendered
698- if ( ! field ) {
699- setFieldError ( path , messages ) ;
700-
701- return validation ;
702- }
703-
704- // always update the valid flag regardless of the mode
705- applyFieldMutation ( field , f => ( f . meta . valid = fieldResult . valid ) ) ;
706- if ( mode === 'silent' ) {
707- return validation ;
708- }
709-
710- const wasValidated = Array . isArray ( field ) ? field . some ( f => f . meta . validated ) : field . meta . validated ;
711- if ( mode === 'validated-only' && ! wasValidated ) {
712- return validation ;
713- }
714-
715- applyFieldMutation ( field , f => f . setState ( { errors : fieldResult . errors } ) ) ;
716-
717- return validation ;
718- } ,
719- { valid : formResult . valid , results : { } , errors : { } } as FormValidationResult < TValues >
720- ) ;
721- }
722-
723728 const submitForm = handleSubmit ( ( _ , { evt } ) => {
724729 if ( isFormSubmitEvent ( evt ) ) {
725730 evt . target . submit ( ) ;
0 commit comments