1
- import React , { useMemo } from "react" ;
1
+ import React from "react" ;
2
2
import { InputHTMLAttributes } from "react" ;
3
3
import { DefaultError , DefaultState , FormState } from "../form" ;
4
4
import { useListener } from "../hooks" ;
@@ -32,12 +32,96 @@ export type FormInputType =
32
32
| "tel"
33
33
| "range" ;
34
34
35
+ function defaultSerializer ( currentValue : any , props : FormInputProps < any > ) : boolean | string {
36
+ switch ( props . type ) {
37
+ case "datetime-local" :
38
+ case "date" : {
39
+ let dateValue = currentValue as any ;
40
+ if ( typeof dateValue === "string" ) {
41
+ let ni = parseInt ( dateValue ) ;
42
+ if ( ! isNaN ( ni ) ) dateValue = ni ;
43
+ }
44
+ let date = new Date ( dateValue ) ;
45
+ if ( ! isNaN ( date . getTime ( ) ) ) {
46
+ return date ?. toISOString ( ) . split ( "T" ) [ 0 ] ?? "" ;
47
+ } else {
48
+ return "" ;
49
+ }
50
+ break ;
51
+ }
52
+ case "radio" : {
53
+ return currentValue === props . value ;
54
+ }
55
+ case "checkbox" : {
56
+ if ( props . setNullOnUncheck ) {
57
+ return currentValue !== null ;
58
+ } else if ( props . setUndefinedOnUncheck ) {
59
+ return currentValue !== undefined ;
60
+ } else if ( props . value !== undefined ) {
61
+ return ( Array . isArray ( currentValue ) ? currentValue : [ ] ) . includes ( props . value as never ) ;
62
+ } else {
63
+ return ! ! currentValue ;
64
+ }
65
+ }
66
+ default : {
67
+ return ( currentValue ?? "" ) + "" ;
68
+ }
69
+ }
70
+ }
71
+
72
+ function defaultDeserializer ( inputValue : string , inputChecked : boolean , currentValue : any , props : FormInputProps < any > ) {
73
+ switch ( props . type ) {
74
+ case "number" : {
75
+ return parseFloat ( inputValue ) as any ;
76
+ }
77
+ case "datetime-local" :
78
+ case "date" : {
79
+ if ( inputValue ) {
80
+ let d = new Date ( inputValue ) ;
81
+ return ( props . dateAsNumber ? d . getTime ( ) : d ) as any ;
82
+ } else {
83
+ return null as any ;
84
+ }
85
+ }
86
+ case "radio" : {
87
+ // Enum field
88
+ if ( inputChecked ) {
89
+ return props . value as any ;
90
+ }
91
+ return currentValue ;
92
+ }
93
+ case "checkbox" : {
94
+ if ( props . setNullOnUncheck || props . setUndefinedOnUncheck ) {
95
+ if ( inputChecked && props . value === undefined && process . env . NODE_ENV === "development" ) {
96
+ console . error (
97
+ "Checkbox using setNullOnUncheck got checked but a value to set was not found, please provide a value to the value prop."
98
+ ) ;
99
+ }
100
+ return inputChecked ? props . value : ( ( props . setNullOnUncheck ? null : undefined ) as any ) ;
101
+ } else if ( props . value !== undefined ) {
102
+ // Primitive array field
103
+ let arr = Array . isArray ( currentValue ) ? [ ...currentValue ] : [ ] ;
104
+ if ( inputChecked ) arr . push ( inputValue ) ;
105
+ else arr . splice ( arr . indexOf ( inputValue ) , 1 ) ;
106
+ return arr as any ;
107
+ } else {
108
+ // Boolean field
109
+ return inputChecked as any ;
110
+ }
111
+ }
112
+ default : {
113
+ // String field
114
+ return inputValue as any ;
115
+ }
116
+ }
117
+ }
118
+
35
119
export type FormInputProps <
36
120
T extends object ,
37
- State ,
38
- Error extends string ,
39
- K extends keyof T ,
40
- Value extends T [ K ] | T [ K ] [ keyof T [ K ] ]
121
+ K extends keyof T = keyof T ,
122
+ Value extends T [ K ] | T [ K ] [ keyof T [ K ] ] = any ,
123
+ State = DefaultState ,
124
+ Error extends string = string
41
125
> = BaldInputProps & {
42
126
form : FormState < T , State , Error > ;
43
127
name : K ;
@@ -67,78 +151,35 @@ export function FormInput<
67
151
Value extends T [ K ] | T [ K ] [ keyof T [ K ] ] ,
68
152
State extends DefaultState = DefaultState ,
69
153
Error extends string = DefaultError
70
- > ( {
71
- form,
72
- name,
73
- style,
74
- className,
75
- disableOnSubmitting,
76
- dateAsNumber,
77
- errorClassName,
78
- errorStyle,
79
- dirtyClassName,
80
- dirtyStyle,
81
- setUndefinedOnUncheck,
82
- setNullOnUncheck,
83
- hideWhenNull,
84
- value : inputValue ,
85
- checked : inputChecked ,
86
- ...rest
87
- } : FormInputProps < T , State , Error , K , Value > ) {
88
- const { value : currentValue , error, dirty, state, setValue, defaultValue } = useListener ( form , name ) ;
154
+ > ( props : FormInputProps < T , K , Value , State , Error > ) {
155
+ const {
156
+ value : inputValue ,
157
+ checked : inputChecked ,
158
+ form,
159
+ hideWhenNull,
160
+ dirtyStyle,
161
+ errorStyle,
162
+ dirtyClassName,
163
+ errorClassName,
164
+ setNullOnUncheck,
165
+ setUndefinedOnUncheck,
166
+ className,
167
+ disableOnSubmitting,
168
+ style,
169
+ name,
170
+ type,
171
+ ...rest
172
+ } = props ;
173
+ const { value : currentValue , error, dirty, state, setValue } = useListener ( form , name ) ;
89
174
90
- let [ inValue , inChecked ] = useMemo ( ( ) => {
91
- let inValue = undefined ,
92
- inChecked = undefined ;
93
- switch ( rest . type ) {
94
- case "number" : {
95
- inValue = ( currentValue ?? "" ) + "" ;
96
- break ;
97
- }
98
- case "datetime-local" :
99
- case "date" : {
100
- let n = currentValue as any ;
101
- if ( typeof n === "string" ) {
102
- let ni = parseInt ( n ) ;
103
- if ( ! isNaN ( ni ) ) n = ni ;
104
- }
105
- let d = new Date ( n ) ;
106
- if ( d . getTime ( ) === d . getTime ( ) ) {
107
- // Trick to check if date is valid: NaN === NaN returns false
108
- inValue = d ?. toISOString ( ) . split ( "T" ) [ 0 ] ?? "" ;
109
- } else {
110
- inValue = "" ;
111
- }
112
- break ;
113
- }
114
- case "radio" : {
115
- inChecked = currentValue === inputValue ;
116
- break ;
117
- }
118
- case "checkbox" : {
119
- if ( setNullOnUncheck ) {
120
- inChecked = currentValue !== null ;
121
- } else if ( setUndefinedOnUncheck ) {
122
- inChecked = currentValue !== undefined ;
123
- } else if ( inputValue !== undefined ) {
124
- inChecked = ( Array . isArray ( currentValue ) ? currentValue : [ ] ) . includes ( inputValue as never ) ;
125
- } else {
126
- inChecked = ! ! currentValue ;
127
- }
128
- break ;
129
- }
130
- default : {
131
- inValue = ( currentValue ?? "" ) + "" ;
132
- break ;
133
- }
134
- }
135
- return [ inValue , inChecked ] ;
136
- } , [ rest . type , currentValue , inputValue ] ) ;
175
+ let valueChecked = defaultSerializer ( currentValue , props ) ;
137
176
138
- if ( hideWhenNull && ( currentValue === null || currentValue === undefined ) ) return null ;
177
+ if ( process . env . NODE_ENV === "development" ) {
178
+ if ( ( setNullOnUncheck || setUndefinedOnUncheck ) && type !== "checkbox" )
179
+ console . error ( "setNullOnUncheck/setUndefinedOnUncheck only has an effect on checkboxes." ) ;
180
+ }
139
181
140
- if ( ( setNullOnUncheck || setUndefinedOnUncheck ) && rest . type !== "checkbox" )
141
- console . warn ( "setNullOnUncheck/setUndefinedOnUncheck only has an effect on checkboxes." ) ;
182
+ if ( hideWhenNull && ( currentValue === null || currentValue === undefined ) ) return null ;
142
183
143
184
return (
144
185
< input
@@ -149,62 +190,16 @@ export function FormInput<
149
190
} }
150
191
className = { getClassName ( className , dirty && ( dirtyClassName ?? DEFAULT_DIRTY_CLASS ) , error && ( errorClassName ?? DEFAULT_ERROR_CLASS ) ) }
151
192
disabled = { ( disableOnSubmitting ?? true ) && state . isSubmitting }
152
- value = { inValue }
153
- checked = { inChecked }
193
+ value = { typeof valueChecked === "string" ? valueChecked : ( inputValue as any ) }
194
+ checked = { typeof valueChecked === "boolean" ? valueChecked : inputChecked }
154
195
onChange = { ( ev ) => {
155
- let newValue = ev . target . value ;
156
- let newChecked = ev . target . checked ;
157
- switch ( rest . type ) {
158
- case "number" : {
159
- setValue ( parseFloat ( newValue ) as any ) ;
160
- return ;
161
- }
162
- case "datetime-local" :
163
- case "date" : {
164
- if ( newValue ) {
165
- let d = new Date ( newValue ) ;
166
- setValue ( ( dateAsNumber ? d . getTime ( ) : d ) as any ) ;
167
- } else {
168
- setValue ( null as any ) ;
169
- }
170
- return ;
171
- }
172
- case "radio" : {
173
- // Enum field
174
- if ( newChecked ) {
175
- setValue ( inputValue as any ) ;
176
- }
177
- return ;
178
- }
179
- case "checkbox" : {
180
- if ( setNullOnUncheck || setUndefinedOnUncheck ) {
181
- if ( newChecked && inputValue === undefined && ! defaultValue )
182
- console . warn (
183
- "Toggling checkbox using setNullOnUncheck got checked but a value to set was not found, please provide the value prop"
184
- ) ;
185
- setValue (
186
- newChecked ? ( inputValue !== undefined ? inputValue : defaultValue ) : ( ( setNullOnUncheck ? null : undefined ) as any )
187
- ) ;
188
- } else if ( inputValue !== undefined ) {
189
- // Primitive array field
190
- let arr = Array . isArray ( currentValue ) ? [ ...currentValue ] : [ ] ;
191
- if ( newChecked ) arr . push ( inputValue ) ;
192
- else arr . splice ( arr . indexOf ( inputValue ) , 1 ) ;
193
- setValue ( arr as any ) ;
194
- } else {
195
- // Boolean field
196
- setValue ( newChecked as any ) ;
197
- }
198
- return ;
199
- }
200
- default : {
201
- // String field
202
- setValue ( newValue as any ) ;
203
- return ;
204
- }
205
- }
196
+ console . log ( "deserializing" , inputValue , ev . target . value , ev . target . checked , currentValue ) ;
197
+ let dse = defaultDeserializer ( ev . target . value , ev . target . checked , currentValue , props ) ;
198
+ console . log ( "deserialize" , JSON . stringify ( dse ) ) ;
199
+ setValue ( dse ) ;
206
200
} }
207
201
name = { name + "" }
202
+ type = { type }
208
203
{ ...rest }
209
204
/>
210
205
) ;
0 commit comments