1- import React , { useMemo } from "react" ;
1+ import React from "react" ;
22import { InputHTMLAttributes } from "react" ;
33import { DefaultError , DefaultState , FormState } from "../form" ;
44import { useListener } from "../hooks" ;
@@ -32,12 +32,96 @@ export type FormInputType =
3232 | "tel"
3333 | "range" ;
3434
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+
35119export type FormInputProps <
36120 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
41125> = BaldInputProps & {
42126 form : FormState < T , State , Error > ;
43127 name : K ;
@@ -67,78 +151,35 @@ export function FormInput<
67151 Value extends T [ K ] | T [ K ] [ keyof T [ K ] ] ,
68152 State extends DefaultState = DefaultState ,
69153 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 ) ;
89174
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 ) ;
137176
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+ }
139181
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 ;
142183
143184 return (
144185 < input
@@ -149,62 +190,16 @@ export function FormInput<
149190 } }
150191 className = { getClassName ( className , dirty && ( dirtyClassName ?? DEFAULT_DIRTY_CLASS ) , error && ( errorClassName ?? DEFAULT_ERROR_CLASS ) ) }
151192 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 }
154195 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 ) ;
206200 } }
207201 name = { name + "" }
202+ type = { type }
208203 { ...rest }
209204 />
210205 ) ;
0 commit comments