@@ -9,89 +9,103 @@ export type FormProps = React.PropsWithChildren<{
9
9
onSubmit : ( ) => boolean | Promise < boolean > ;
10
10
} > ;
11
11
12
- export const Form = ( { children, onSubmit } : FormProps ) => {
13
- const refs = React . useRef < FormRef [ ] > ( [ ] ) ;
14
- const { setFocus } = useFocus ( ) ;
15
-
16
- const checks = __DEV__ ? useChecks ?.( ) : undefined ;
17
-
18
- const focusField = ( nextField ?: Partial < FormRef > | undefined ) => {
19
- /**
20
- * Refs passed as prop have another ".current"
21
- */
22
- const nextRefElement = nextField ?. ref ?. current ?. current
23
- ? nextField ?. ref . current
24
- : nextField ?. ref ;
25
-
26
- const callFocus =
27
- // @ts -ignore
28
- nextRefElement ?. current ?. focus &&
29
- nextField ?. hasFocusCallback &&
30
- nextField ?. isEditable ;
12
+ export type FormActions = {
13
+ focusFirstInvalidField : ( ) => void ;
14
+ } ;
31
15
32
- __DEV__ &&
33
- nextRefElement == null &&
34
- checks ?. logResult ( 'nextRefElement' , {
35
- message :
36
- 'No next field found. Make sure you wrapped your form inside the <Form /> component' ,
37
- rule : 'NO_UNDEFINED' ,
38
- } ) ;
16
+ export const Form = React . forwardRef < FormActions , FormProps > (
17
+ ( { children, onSubmit } , ref ) => {
18
+ const refs = React . useRef < FormRef [ ] > ( [ ] ) ;
19
+ const { setFocus } = useFocus ( ) ;
20
+
21
+ const checks = __DEV__ ? useChecks ?.( ) : undefined ;
39
22
40
- if ( callFocus ) {
23
+ const focusField = ( nextField ?: Partial < FormRef > | undefined ) => {
41
24
/**
42
- * On some apps, if we call focus immediately and the field is already focused we lose the focus.
43
- * Same happens if we do not call `focus` if the field is already focused.
25
+ * Refs passed as prop have another ".current"
44
26
*/
45
- const timeoutValue = isFocused ( nextRefElement . current ) ? 50 : 0 ;
46
-
47
- setTimeout ( ( ) => {
48
- nextRefElement ?. current ?. focus ( ) ;
49
-
50
- __DEV__ &&
51
- nextRefElement &&
52
- checks ?. checkFocusTrap ( {
53
- ref : nextRefElement ,
54
- shouldHaveFocus : true ,
55
- } ) ;
56
- } , timeoutValue ) ;
57
- } else if ( nextRefElement ?. current ) {
58
- setFocus ( nextRefElement ?. current ) ;
59
- }
60
- } ;
61
-
62
- const submitForm = async ( ) => {
63
- const isValid = await onSubmit ( ) ;
64
-
65
- if ( isValid ) {
66
- return ;
67
- }
68
-
69
- InteractionManager . runAfterInteractions ( ( ) => {
70
- setTimeout ( ( ) => {
71
- const fieldWithError = ( refs . current || [ ] ) . find (
72
- ref => ref . hasValidation && ref . hasError ,
73
- ) ;
74
-
75
- __DEV__ &&
76
- fieldWithError == null &&
77
- checks ?. logResult ( 'Form' , {
78
- message :
79
- 'The form validation has failed, but no component with error was found' ,
80
- rule : 'NO_UNDEFINED' ,
81
- } ) ;
82
-
83
- focusField ( fieldWithError ) ;
84
- } , 0 ) ;
85
- } ) ;
86
- } ;
87
-
88
- return (
89
- < FormContext . Provider
90
- value = { { refs : refs . current , submitForm, focusField } } >
91
- { children }
92
- </ FormContext . Provider >
93
- ) ;
94
- } ;
27
+ const nextRefElement = nextField ?. ref ?. current ?. current
28
+ ? nextField ?. ref . current
29
+ : nextField ?. ref ;
30
+
31
+ const callFocus =
32
+ // @ts -ignore
33
+ nextRefElement ?. current ?. focus &&
34
+ nextField ?. hasFocusCallback &&
35
+ nextField ?. isEditable ;
36
+
37
+ __DEV__ &&
38
+ nextRefElement == null &&
39
+ checks ?. logResult ( 'nextRefElement' , {
40
+ message :
41
+ 'No next field found. Make sure you wrapped your form inside the <Form /> component' ,
42
+ rule : 'NO_UNDEFINED' ,
43
+ } ) ;
44
+
45
+ if ( callFocus ) {
46
+ /**
47
+ * On some apps, if we call focus immediately and the field is already focused we lose the focus.
48
+ * Same happens if we do not call `focus` if the field is already focused.
49
+ */
50
+ const timeoutValue = isFocused ( nextRefElement . current ) ? 50 : 0 ;
51
+
52
+ setTimeout ( ( ) => {
53
+ nextRefElement ?. current ?. focus ( ) ;
54
+
55
+ __DEV__ &&
56
+ nextRefElement &&
57
+ checks ?. checkFocusTrap ( {
58
+ ref : nextRefElement ,
59
+ shouldHaveFocus : true ,
60
+ } ) ;
61
+ } , timeoutValue ) ;
62
+ } else if ( nextRefElement ?. current ) {
63
+ setFocus ( nextRefElement ?. current ) ;
64
+ }
65
+ } ;
66
+
67
+ const submitForm = async ( ) => {
68
+ const isValid = await onSubmit ( ) ;
69
+
70
+ if ( isValid ) {
71
+ return ;
72
+ }
73
+
74
+ focusFirstInvalidField ( ) ;
75
+ } ;
76
+
77
+ const focusFirstInvalidField = ( ) => {
78
+ InteractionManager . runAfterInteractions ( ( ) => {
79
+ setTimeout ( ( ) => {
80
+ const fieldWithError = ( refs . current || [ ] ) . find (
81
+ fieldRef => fieldRef . hasValidation && fieldRef . hasError ,
82
+ ) ;
83
+
84
+ __DEV__ &&
85
+ fieldWithError == null &&
86
+ checks ?. logResult ( 'Form' , {
87
+ message :
88
+ 'The form validation has failed, but no component with error was found' ,
89
+ rule : 'NO_UNDEFINED' ,
90
+ } ) ;
91
+
92
+ focusField ( fieldWithError ) ;
93
+ } , 0 ) ;
94
+ } ) ;
95
+ } ;
96
+
97
+ React . useImperativeHandle ( ref , ( ) => ( {
98
+ focusFirstInvalidField,
99
+ } ) ) ;
100
+
101
+ return (
102
+ < FormContext . Provider
103
+ value = { { refs : refs . current , submitForm, focusField } } >
104
+ { children }
105
+ </ FormContext . Provider >
106
+ ) ;
107
+ } ,
108
+ ) ;
95
109
96
110
export type FormContextValue = {
97
111
refs ?: FormRef [ ] ;
0 commit comments