1
- import { isNotFalsy , mapValues } from '@seedcompany/common' ;
1
+ import { groupBy , isNotFalsy , mapValues } from '@seedcompany/common' ;
2
2
import { FORM_ERROR , FormApi , setIn , SubmissionErrors } from 'final-form' ;
3
3
import { isValidElement , ReactElement } from 'react' ;
4
4
import { Promisable } from 'type-fest' ;
@@ -63,6 +63,8 @@ export type ErrorHandlerResult =
63
63
interface HandlerUtils {
64
64
/** Does the form have this field registered? */
65
65
hasField : ( field : string ) => boolean ;
66
+ /** Finds a registered field matching the given name */
67
+ findField : ( field : string | RegExp ) => string | undefined ;
66
68
}
67
69
68
70
const expandDotNotation = ( input : Record < string , any > ) =>
@@ -87,6 +89,50 @@ export const defaultHandlers = {
87
89
e . field && hasField ( e . field ) ? setIn ( { } , e . field , e . message ) : next ( e ) ,
88
90
Duplicate : ( e , next , { hasField } ) =>
89
91
hasField ( e . field ) ? setIn ( { } , e . field , 'Already in use' ) : next ( e ) ,
92
+ MissingRequiredFields : ( e , next , { findField } ) => {
93
+ const { in : inForm , out : outOfForm } = e . missing . reduce (
94
+ ( res , missing ) => {
95
+ const formFieldName =
96
+ findField ( missing . field ) ??
97
+ // Also find with a path prefix: mouStart -> project.mouStart
98
+ findField ( RegExp ( `\\.${ missing . field } $` ) ) ??
99
+ // Also find with Id suffix: primaryLocation -> primaryLocationId
100
+ findField ( missing . field + 'Id' ) ??
101
+ findField ( RegExp ( `\\.${ missing . field } Id$` ) ) ;
102
+ if ( formFieldName ) {
103
+ res . in . push ( {
104
+ ...missing ,
105
+ field : formFieldName ,
106
+ } ) ;
107
+ } else {
108
+ res . out . push ( missing ) ;
109
+ }
110
+ return res ;
111
+ } ,
112
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
113
+ { in : [ ] , out : [ ] } as Record <
114
+ 'in' | 'out' ,
115
+ Array < ( typeof e . missing ) [ number ] >
116
+ >
117
+ ) ;
118
+ return {
119
+ ...inForm . reduce (
120
+ ( res , missing ) =>
121
+ setIn ( res , missing . field , 'Required when ' + missing . description ) ,
122
+ { }
123
+ ) ,
124
+ ...( outOfForm . length > 0 && {
125
+ [ FORM_ERROR ] : groupBy ( outOfForm , ( x ) => x . description )
126
+ . map ( ( fields ) =>
127
+ [
128
+ `The following fields are required when ${ fields [ 0 ] . description } :` ,
129
+ ...fields . map ( ( x ) => ` - ${ x . field } ` ) ,
130
+ ] . join ( '\n' )
131
+ )
132
+ . join ( '\n\n' ) ,
133
+ } ) ,
134
+ } ;
135
+ } ,
90
136
91
137
// Assume server errors are handled separately
92
138
// Return failure but no error message
@@ -109,6 +155,7 @@ export const handleFormError = async <T, P>(
109
155
110
156
const utils : HandlerUtils = {
111
157
hasField : ( field ) => form . getRegisteredFields ( ) . includes ( field ) ,
158
+ findField : makeFindField ( form . getRegisteredFields ( ) ) ,
112
159
} ;
113
160
114
161
const mergedHandlers = { ...defaultHandlers , ...handlers } ;
@@ -149,3 +196,8 @@ const resolveHandler =
149
196
? { [ FORM_ERROR ] : result }
150
197
: result ;
151
198
} ;
199
+
200
+ const makeFindField = ( fields : readonly string [ ] ) => ( field : string | RegExp ) =>
201
+ typeof field === 'string' && fields . includes ( field )
202
+ ? field
203
+ : fields . find ( ( f ) => f . match ( field ) ) ;
0 commit comments