1
- import React from 'react' ;
1
+ import React , { useEffect , useReducer } from 'react' ;
2
2
import PropTypes from 'prop-types' ;
3
3
import lodashIsEmpty from 'lodash/isEmpty' ;
4
- import { FormSpy } from 'react-final-form' ;
5
4
import get from 'lodash/get' ;
5
+ import isEqual from 'lodash/isEqual' ;
6
+
7
+ import useFormApi from '../files/use-form-api' ;
6
8
7
9
const isEmptyValue = ( value ) => ( typeof value === 'number' || value === true ? false : lodashIsEmpty ( value ) ) ;
8
10
@@ -27,48 +29,156 @@ const fieldCondition = (value, { is, isNotEmpty, isEmpty, pattern, notMatch, fla
27
29
} ;
28
30
29
31
export const parseCondition = ( condition , values ) => {
32
+ let positiveResult = {
33
+ visible : true ,
34
+ ...condition . then ,
35
+ result : true
36
+ } ;
37
+
38
+ let negativeResult = {
39
+ visible : false ,
40
+ ...condition . else ,
41
+ result : false
42
+ } ;
43
+
30
44
if ( Array . isArray ( condition ) ) {
31
- return ! condition . map ( ( condition ) => parseCondition ( condition , values ) ) . some ( ( result ) => result === false ) ;
45
+ return ! condition . map ( ( condition ) => parseCondition ( condition , values ) ) . some ( ( { result } ) => result === false ) ? positiveResult : negativeResult ;
32
46
}
33
47
34
48
if ( condition . and ) {
35
- return condition . and . map ( ( condition ) => parseCondition ( condition , values ) ) . every ( ( result ) => result === true ) ;
49
+ return ! condition . and . map ( ( condition ) => parseCondition ( condition , values ) ) . some ( ( { result } ) => result === false )
50
+ ? positiveResult
51
+ : negativeResult ;
52
+ }
53
+
54
+ if ( condition . sequence ) {
55
+ return condition . sequence . reduce (
56
+ ( acc , curr ) => {
57
+ const result = parseCondition ( curr , values ) ;
58
+
59
+ return {
60
+ sets : [ ...acc . sets , ...( result . set ? [ result . set ] : [ ] ) ] ,
61
+ visible : acc . visible || result . visible ,
62
+ result : acc . result || result . result
63
+ } ;
64
+ } ,
65
+ { ...negativeResult , sets : [ ] }
66
+ ) ;
36
67
}
37
68
38
69
if ( condition . or ) {
39
- return condition . or . map ( ( condition ) => parseCondition ( condition , values ) ) . some ( ( result ) => result === true ) ;
70
+ return condition . or . map ( ( condition ) => parseCondition ( condition , values ) ) . some ( ( { result } ) => result === true ) ? positiveResult : negativeResult ;
40
71
}
41
72
42
73
if ( condition . not ) {
43
- return ! parseCondition ( condition . not , values ) ;
74
+ return ! parseCondition ( condition . not , values ) . result ? positiveResult : negativeResult ;
44
75
}
45
76
46
77
if ( typeof condition . when === 'string' ) {
47
- return fieldCondition ( get ( values , condition . when ) , condition ) ;
78
+ return fieldCondition ( get ( values , condition . when ) , condition ) ? positiveResult : negativeResult ;
48
79
}
49
80
50
81
if ( Array . isArray ( condition . when ) ) {
51
- return ! ! condition . when . map ( ( fieldName ) => fieldCondition ( get ( values , fieldName ) , condition ) ) . find ( ( condition ) => ! ! condition ) ;
82
+ return condition . when . map ( ( fieldName ) => fieldCondition ( get ( values , fieldName ) , condition ) ) . find ( ( condition ) => ! ! condition )
83
+ ? positiveResult
84
+ : negativeResult ;
52
85
}
53
86
54
- return false ;
87
+ return negativeResult ;
55
88
} ;
56
89
57
- const Condition = ( { condition, children } ) => < FormSpy > { ( { values } ) => ( parseCondition ( condition , values ) ? children : null ) } </ FormSpy > ;
90
+ export const reducer = ( state , { type, sets } ) => {
91
+ switch ( type ) {
92
+ case 'formResetted' :
93
+ return {
94
+ ...state ,
95
+ initial : true
96
+ } ;
97
+ case 'rememberSets' :
98
+ return {
99
+ ...state ,
100
+ initial : false ,
101
+ sets
102
+ } ;
103
+ default :
104
+ return state ;
105
+ }
106
+ } ;
107
+
108
+ const Condition = React . memo (
109
+ ( { condition, children, values } ) => {
110
+ const formOptions = useFormApi ( ) ;
111
+ const dirty = formOptions . getState ( ) . dirty ;
112
+
113
+ const [ state , dispatch ] = useReducer ( reducer , {
114
+ sets : [ ] ,
115
+ initial : true
116
+ } ) ;
117
+
118
+ const conditionResult = parseCondition ( condition , values , formOptions ) ;
119
+ const setters = conditionResult . set ? [ conditionResult . set ] : conditionResult . sets ;
120
+
121
+ useEffect ( ( ) => {
122
+ if ( ! dirty ) {
123
+ dispatch ( { type : 'formResetted' } ) ;
124
+ }
125
+ } , [ dirty ] ) ;
126
+
127
+ useEffect ( ( ) => {
128
+ if ( setters && setters . length > 0 && ( state . initial || ! isEqual ( setters , state . sets ) ) ) {
129
+ setters . forEach ( ( setter , index ) => {
130
+ if ( setter && ( state . initial || ! isEqual ( setter , state . sets [ index ] ) ) ) {
131
+ setTimeout ( ( ) => {
132
+ formOptions . batch ( ( ) => {
133
+ Object . entries ( setter ) . forEach ( ( [ name , value ] ) => {
134
+ formOptions . change ( name , value ) ;
135
+ } ) ;
136
+ } ) ;
137
+ } ) ;
138
+ }
139
+ } ) ;
140
+ dispatch ( { type : 'rememberSets' , sets : setters } ) ;
141
+ }
142
+ } , [ setters , state . initial ] ) ;
143
+
144
+ return conditionResult . visible ? children : null ;
145
+ } ,
146
+ ( a , b ) => isEqual ( a . values , b . values ) && isEqual ( a . condition , b . condition )
147
+ ) ;
58
148
59
149
const conditionProps = {
60
- when : PropTypes . string . isRequired ,
61
- is : PropTypes . oneOfType ( [ PropTypes . array , PropTypes . string , PropTypes . object , PropTypes . number , PropTypes . bool ] ) . isRequired ,
150
+ when : PropTypes . string ,
151
+ is : PropTypes . oneOfType ( [ PropTypes . array , PropTypes . string , PropTypes . object , PropTypes . number , PropTypes . bool ] ) ,
62
152
isNotEmpty : PropTypes . bool ,
63
153
isEmpty : PropTypes . bool ,
64
- children : PropTypes . oneOf ( [ PropTypes . node , PropTypes . arrayOf ( PropTypes . node ) ] ) . isRequired ,
65
154
pattern : PropTypes . oneOf ( [ PropTypes . string , PropTypes . instanceOf ( RegExp ) ] ) ,
66
- notMatch : PropTypes . any
155
+ notMatch : PropTypes . any ,
156
+ then : PropTypes . shape ( {
157
+ visible : PropTypes . bool ,
158
+ set : PropTypes . object
159
+ } ) ,
160
+ else : PropTypes . shape ( {
161
+ visible : PropTypes . bool ,
162
+ set : PropTypes . object
163
+ } )
164
+ } ;
165
+
166
+ const nestedConditions = {
167
+ or : PropTypes . oneOfType ( [ PropTypes . shape ( conditionProps ) , PropTypes . arrayOf ( PropTypes . shape ( conditionProps ) ) ] ) ,
168
+ and : PropTypes . oneOfType ( [ PropTypes . shape ( conditionProps ) , PropTypes . arrayOf ( PropTypes . shape ( conditionProps ) ) ] ) ,
169
+ not : PropTypes . oneOfType ( [ PropTypes . shape ( conditionProps ) , PropTypes . arrayOf ( PropTypes . shape ( conditionProps ) ) ] ) ,
170
+ sequence : PropTypes . arrayOf ( PropTypes . shape ( conditionProps ) )
171
+ } ;
172
+
173
+ const conditionsProps = {
174
+ ...conditionProps ,
175
+ ...nestedConditions
67
176
} ;
68
177
69
178
Condition . propTypes = {
70
- condition : PropTypes . oneOfType ( [ PropTypes . shape ( conditionProps ) , PropTypes . arrayOf ( PropTypes . shape ( conditionProps ) ) ] ) ,
71
- children : PropTypes . oneOf ( [ PropTypes . node , PropTypes . arrayOf ( PropTypes . node ) ] ) . isRequired
179
+ condition : PropTypes . oneOfType ( [ PropTypes . shape ( conditionsProps ) , PropTypes . arrayOf ( PropTypes . shape ( conditionsProps ) ) ] ) ,
180
+ children : PropTypes . oneOfType ( [ PropTypes . node , PropTypes . arrayOf ( PropTypes . node ) ] ) . isRequired ,
181
+ values : PropTypes . object . isRequired
72
182
} ;
73
183
74
184
export default Condition ;
0 commit comments