Skip to content

Commit 2425b9e

Browse files
committed
Serializer & Deserializer types
1 parent cf68431 commit 2425b9e

File tree

1 file changed

+38
-27
lines changed

1 file changed

+38
-27
lines changed

src/elements/FormInput.tsx

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,22 @@ export type FormInputType =
3232
| "tel"
3333
| "range";
3434

35-
function defaultSerializer(currentValue: any, props: FormInputProps<any>): boolean | string {
35+
export type Serializer<T extends object, K extends keyof T, State = DefaultState, Error extends string = string> = (
36+
currentValue: T[K] | T[K][keyof T[K]],
37+
props: FormInputProps<T, K, State, Error>
38+
) => boolean | string;
39+
40+
export type Deserializer<T extends object, K extends keyof T, State = DefaultState, Error extends string = string> = (
41+
inputValue: string,
42+
inputChecked: boolean,
43+
currentValue: T[K] | T[K][keyof T[K]],
44+
props: FormInputProps<T, K, State, Error>
45+
) => T[K] | T[K][keyof T[K]];
46+
47+
function defaultSerializer<T extends object, K extends keyof T, State = DefaultState, Error extends string = string>(
48+
currentValue: T[K] | T[K][keyof T[K]],
49+
props: FormInputProps<T, K, State, Error>
50+
): boolean | string {
3651
switch (props.type) {
3752
case "datetime-local":
3853
case "date": {
@@ -69,7 +84,12 @@ function defaultSerializer(currentValue: any, props: FormInputProps<any>): boole
6984
}
7085
}
7186

72-
function defaultDeserializer(inputValue: string, inputChecked: boolean, currentValue: any, props: FormInputProps<any>) {
87+
function defaultDeserializer<T extends object, K extends keyof T, State = DefaultState, Error extends string = string>(
88+
inputValue: string,
89+
inputChecked: boolean,
90+
currentValue: any,
91+
props: FormInputProps<T, K, State, Error>
92+
) {
7393
switch (props.type) {
7494
case "number": {
7595
return parseFloat(inputValue) as any;
@@ -101,8 +121,8 @@ function defaultDeserializer(inputValue: string, inputChecked: boolean, currentV
101121
} else if (props.value !== undefined) {
102122
// Primitive array field
103123
let arr = Array.isArray(currentValue) ? [...currentValue] : [];
104-
if (inputChecked) arr.push(inputValue);
105-
else arr.splice(arr.indexOf(inputValue), 1);
124+
if (inputChecked) arr.push(props.value);
125+
else arr.splice(arr.indexOf(props.value), 1);
106126
return arr as any;
107127
} else {
108128
// Boolean field
@@ -116,17 +136,13 @@ function defaultDeserializer(inputValue: string, inputChecked: boolean, currentV
116136
}
117137
}
118138

119-
export type FormInputProps<
120-
T extends object,
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
125-
> = BaldInputProps & {
139+
export type FormInputProps<T extends object, K extends keyof T = keyof T, State = DefaultState, Error extends string = string> = BaldInputProps & {
126140
form: FormState<T, State, Error>;
127141
name: K;
128142
type?: FormInputType;
129-
value?: Value;
143+
value?: T[K] | T[K][keyof T[K]];
144+
serializer?: Serializer<T, K, State, Error>;
145+
deserializer?: Deserializer<T, K, State, Error>;
130146
errorClassName?: string;
131147
errorStyle?: React.CSSProperties;
132148
dirtyClassName?: string;
@@ -143,15 +159,11 @@ export type FormInputProps<
143159
*
144160
* **FormSelect**, **FormTextArea** and **FormError** are also available.
145161
*
146-
* When this component does not satisfy your needs, you can always [implement your own](https://typed-react-form.codestix.nl/docs/Custom-inputs#example-custom-input).
162+
* When this component does not satisfy your needs, you can always [implement your own](https://typed-react-form.codestix.nl/docs/Custom-input#example-custom-input).
147163
*/
148-
export function FormInput<
149-
T extends object,
150-
K extends keyof T,
151-
Value extends T[K] | T[K][keyof T[K]],
152-
State extends DefaultState = DefaultState,
153-
Error extends string = DefaultError
154-
>(props: FormInputProps<T, K, Value, State, Error>) {
164+
export function FormInput<T extends object, K extends keyof T, State extends DefaultState = DefaultState, Error extends string = DefaultError>(
165+
props: FormInputProps<T, K, State, Error>
166+
) {
155167
const {
156168
value: inputValue,
157169
checked: inputChecked,
@@ -165,14 +177,16 @@ export function FormInput<
165177
setUndefinedOnUncheck,
166178
className,
167179
disableOnSubmitting,
180+
serializer,
181+
deserializer,
168182
style,
169183
name,
170184
type,
171185
...rest
172186
} = props;
173187
const { value: currentValue, error, dirty, state, setValue } = useListener(form, name);
174188

175-
let valueChecked = defaultSerializer(currentValue, props);
189+
let valueChecked = (serializer ?? defaultSerializer)(currentValue, props);
176190

177191
if (process.env.NODE_ENV === "development") {
178192
if ((setNullOnUncheck || setUndefinedOnUncheck) && type !== "checkbox")
@@ -190,13 +204,10 @@ export function FormInput<
190204
}}
191205
className={getClassName(className, dirty && (dirtyClassName ?? DEFAULT_DIRTY_CLASS), error && (errorClassName ?? DEFAULT_ERROR_CLASS))}
192206
disabled={(disableOnSubmitting ?? true) && state.isSubmitting}
193-
value={typeof valueChecked === "string" ? valueChecked : (inputValue as any)}
194-
checked={typeof valueChecked === "boolean" ? valueChecked : inputChecked}
207+
value={typeof valueChecked === "string" ? valueChecked : undefined}
208+
checked={typeof valueChecked === "boolean" ? valueChecked : undefined}
195209
onChange={(ev) => {
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);
210+
setValue((deserializer ?? defaultDeserializer)(ev.target.value, ev.target.checked, currentValue, props));
200211
}}
201212
name={name + ""}
202213
type={type}

0 commit comments

Comments
 (0)