Skip to content

Commit cf68431

Browse files
committed
FormInput defaultSerializer/defaultDeserializer
1 parent 03c0365 commit cf68431

File tree

1 file changed

+122
-127
lines changed

1 file changed

+122
-127
lines changed

src/elements/FormInput.tsx

Lines changed: 122 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useMemo } from "react";
1+
import React from "react";
22
import { InputHTMLAttributes } from "react";
33
import { DefaultError, DefaultState, FormState } from "../form";
44
import { 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+
35119
export 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

Comments
 (0)