Skip to content

Commit

Permalink
Added SerializationOptions
Browse files Browse the repository at this point in the history
  • Loading branch information
lezuber committed Feb 11, 2025
1 parent 2f2111c commit 0582cc6
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 22 deletions.
28 changes: 26 additions & 2 deletions packages/conform-dom/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,22 @@ export type FormState<FormError> = Omit<
dirty: Record<string, boolean>;
};

export type SerializationOptions = {
/**
* Controls how boolean values are serialized
* - 'html' (default): converts true to "on" and false to undefined (standard HTML form behavior)
* - 'preserve': keeps the original boolean values
*/
booleanFormat?: 'html' | 'preserve';

/**
* Controls how dates are serialized
* - 'iso' (default): converts to ISO string
* - 'preserve': keeps the original Date object
*/
dateFormat?: 'iso' | 'preserve';
};

export type FormOptions<Schema, FormError = string[], FormValue = Schema> = {
/**
* The id of the form.
Expand Down Expand Up @@ -186,6 +202,11 @@ export type FormOptions<Schema, FormError = string[], FormValue = Schema> = {
submitter: HTMLInputElement | HTMLButtonElement | null;
formData: FormData;
}) => Submission<Schema, FormError, FormValue>;

/**
* Configure how values are serialized when sent to the form
*/
serialization?: SerializationOptions;
};

export type SubscriptionSubject = {
Expand Down Expand Up @@ -264,7 +285,10 @@ function createFormMeta<Schema, FormError, FormValue>(
): FormMeta<FormError> {
const lastResult = !initialized ? options.lastResult : undefined;
const defaultValue = options.defaultValue
? (serialize(options.defaultValue) as Record<string, unknown>)
? (serialize(options.defaultValue, options.serialization) as Record<
string,
unknown
>)
: {};
const initialValue = lastResult?.initialValue ?? defaultValue;
const result: FormMeta<FormError> = {
Expand Down Expand Up @@ -1015,7 +1039,7 @@ export function createFormContext<
function getControlButtonProps(intent: Intent): ControlButtonProps {
return {
name: INTENT,
value: serializeIntent(intent),
value: serializeIntent(intent, latestOptions.serialization),
form: latestOptions.formId,
formNoValidate: true,
};
Expand Down
53 changes: 35 additions & 18 deletions packages/conform-dom/submission.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import type { DefaultValue, FieldName, FormValue } from './form';
import type {
DefaultValue,
FieldName,
FormValue,
SerializationOptions,
} from './form';
import {
normalize,
flatten,
Expand Down Expand Up @@ -394,22 +399,25 @@ export function getIntent(
return control;
}

export function serializeIntent<Schema>(intent: Intent<Schema>): string {
export function serializeIntent<Schema>(
intent: Intent<Schema>,
options?: SerializationOptions,
): string {
switch (intent.type) {
case 'insert':
return JSON.stringify({
type: intent.type,
payload: {
...intent.payload,
defaultValue: serialize(intent.payload.defaultValue),
defaultValue: serialize(intent.payload.defaultValue, options),
},
});
case 'update':
return JSON.stringify({
type: intent.type,
payload: {
...intent.payload,
value: serialize(intent.payload.value),
value: serialize(intent.payload.value, options),
},
});
default:
Expand Down Expand Up @@ -542,33 +550,42 @@ export function setListState(
});
}

export function serialize<Schema>(defaultValue: Schema): FormValue<Schema> {
export function serialize<Schema>(
defaultValue: Schema,
options?: SerializationOptions,
): FormValue<Schema> {
if (isPlainObject(defaultValue)) {
// @ts-expect-error FIXME
return Object.entries(defaultValue).reduce<Record<string, unknown>>(
(result, [key, value]) => {
result[key] = serialize(value);
result[key] = serialize(value, options);
return result;
},
{},
);
) as FormValue<Schema>;
} else if (Array.isArray(defaultValue)) {
// @ts-expect-error FIXME
return defaultValue.map(serialize);
return defaultValue.map((value) =>
serialize(value, options),
) as FormValue<Schema>;
} else if (defaultValue instanceof Date) {
// @ts-expect-error FIXME
return defaultValue.toISOString();
return (
options?.dateFormat === 'preserve'
? defaultValue
: defaultValue.toISOString()
) as FormValue<Schema>;
} else if (typeof defaultValue === 'boolean') {
// @ts-expect-error FIXME
return defaultValue ? 'on' : undefined;
return (
options?.booleanFormat === 'preserve'
? defaultValue
: defaultValue
? 'on'
: undefined
) as FormValue<Schema>;
} else if (
typeof defaultValue === 'number' ||
typeof defaultValue === 'bigint'
) {
// @ts-expect-error FIXME
return defaultValue.toString();
return defaultValue.toString() as FormValue<Schema>;
} else {
// @ts-expect-error FIXME
return defaultValue ?? undefined;
return (defaultValue ?? undefined) as FormValue<Schema>;
}
}
8 changes: 6 additions & 2 deletions packages/conform-react/integrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,12 +270,16 @@ export function useInputEvent(): {
}

export function useInputValue<
Value extends string | string[] | Array<string | undefined>,
Value extends string | string[] | Array<string | boolean | Date>,
>(options: { key?: Key | null | undefined; initialValue?: Value | undefined }) {
const initializeValue = ():
| (Value extends string ? Value : string | string[])
| undefined => {
if (typeof options.initialValue === 'string') {
if (
typeof options.initialValue === 'string' ||
typeof options.initialValue === 'boolean' ||
options.initialValue instanceof Date
) {
// @ts-expect-error FIXME: To ensure that the type of value is also `string | undefined` if initialValue is not an array
return options.initialValue;
}
Expand Down

0 comments on commit 0582cc6

Please sign in to comment.