@conform-to/react
Version:
Conform view adapter for react
512 lines • 24.6 kB
TypeScript
import type { FieldName, FormError, FormValue, Serialize, SubmissionResult, ValidationAttributes } from '@conform-to/dom/future';
import type { StandardSchemaV1 } from './standard-schema';
export type Prettify<T> = {
[K in keyof T]: T[K];
} & {};
/** Reference to a form element. Can be either a React ref object or a form ID string. */
export type FormRef = React.RefObject<HTMLFormElement | HTMLFieldSetElement | HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement | null> | string;
export type InputSnapshot = {
value?: string | undefined;
options?: string[] | undefined;
checked?: boolean | undefined;
files?: File[] | undefined;
};
export type Control = {
/**
* Current value of the base input. Undefined if the registered input
* is a multi-select, file input, or checkbox group.
*/
value: string | undefined;
/**
* Selected options of the base input. Defined only when the registered input
* is a multi-select or checkbox group.
*/
checked: boolean | undefined;
/**
* Checked state of the base input. Defined only when the registered input
* is a single checkbox or radio input.
*/
options: string[] | undefined;
/**
* Selected files of the base input. Defined only when the registered input
* is a file input.
*/
files: File[] | undefined;
/**
* Registers the base input element(s). Accepts a single input or an array for groups.
*/
register: (element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLCollectionOf<HTMLInputElement> | NodeListOf<HTMLInputElement> | null | undefined) => void;
/**
* A ref object containing the form element associated with the registered input.
* Use this with hooks like useFormData() and useIntent().
*/
formRef: React.RefObject<HTMLFormElement | null>;
/**
* Programmatically updates the input value and emits
* both [change](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event) and
* [input](https://developer.mozilla.org/en-US/docs/Web/API/Element/input_event) events.
*/
change(value: string | string[] | boolean | File | File[] | FileList | null): void;
/**
* Emits [blur](https://developer.mozilla.org/en-US/docs/Web/API/Element/blur_event) and
* [focusout](https://developer.mozilla.org/en-US/docs/Web/API/Element/focusout_event) events.
* Does not actually move focus.
*/
focus(): void;
/**
* Emits [focus](https://developer.mozilla.org/en-US/docs/Web/API/Element/focus_event) and
* [focusin](https://developer.mozilla.org/en-US/docs/Web/API/Element/focusin_event) events.
* This does not move the actual keyboard focus to the input. Use `element.focus()` instead
* if you want to move focus to the input.
*/
blur(): void;
};
export type Selector<FormValue, Result> = (formData: FormValue | null, lastResult: Result | undefined) => Result;
export type UseFormDataOptions = {
/**
* Set to `true` to preserve file inputs and receive a `FormData` object in the selector.
* If omitted or `false`, the selector receives a `URLSearchParams` object, where all values are coerced to strings.
*/
acceptFiles?: boolean;
};
export type DefaultValue<Shape> = Shape extends Record<string, any> ? {
[Key in keyof Shape]?: DefaultValue<Shape[Key]>;
} | null | undefined : Shape extends Array<infer Item> ? Array<DefaultValue<Item>> | null | undefined : Shape extends File | File[] ? null | undefined : Shape | string | null | undefined;
export type FormState<ErrorShape extends BaseErrorShape = DefaultErrorShape> = {
/** Unique identifier that changes on form reset to trigger reset side effects */
resetKey: string;
/** Initial form values */
defaultValue: Record<string, unknown>;
/** Form values that will be synced to the DOM */
targetValue: Record<string, unknown> | null;
/** Form values from server actions, or submitted values when no server intent exists */
serverValue: Record<string, unknown> | null;
/** Validation errors from server-side processing */
serverError: FormError<ErrorShape> | null;
/** Validation errors from client-side validation */
clientError: FormError<ErrorShape> | null;
/** Array of field names that have been touched (validated) */
touchedFields: string[];
/** Mapping of array field names to their item keys for React list rendering */
listKeys: Record<string, string[]>;
};
export type FormAction<ErrorShape extends BaseErrorShape = DefaultErrorShape, Intent extends UnknownIntent | null | undefined = UnknownIntent | null, Context = {}> = SubmissionResult<ErrorShape> & {
type: 'initialize' | 'server' | 'client';
intent: Intent;
ctx: Context;
};
export type GlobalFormOptions = {
/**
* The name of the submit button field that indicates the submission intent.
*
* @default "__intent__"
*/
intentName: string;
/**
* A custom serialization function for converting form data.
*/
serialize: Serialize;
/**
* Determines when validation should run for the first time on a field.
*
* @default "onSubmit"
*/
shouldValidate: 'onSubmit' | 'onBlur' | 'onInput';
/**
* Determines when validation should run again after the field has been validated once.
*
* @default Same as shouldValidate
*/
shouldRevalidate?: 'onSubmit' | 'onBlur' | 'onInput';
/**
* A function that defines custom metadata properties for form fields.
* Useful for integrating with UI libraries or custom form components.
*/
defineCustomMetadata?: CustomMetadataDefinition;
};
export type NonPartial<T> = {
[K in keyof Required<T>]: T[K];
};
export type RequireKey<T, K extends keyof T> = Prettify<T & Pick<NonPartial<T>, K>>;
export type BaseSchemaType = StandardSchemaV1<any, any>;
export type InferInput<Schema> = Schema extends StandardSchemaV1<infer input, any> ? input : unknown;
export type InferOutput<Schema> = Schema extends StandardSchemaV1<any, infer output> ? output : undefined;
export type BaseFormOptions<FormShape extends Record<string, any> = Record<string, any>, ErrorShape extends BaseErrorShape = string extends BaseErrorShape ? string : BaseErrorShape, Value = undefined, Schema = undefined> = {
/** Optional form identifier. If not provided, a unique ID is automatically generated. */
id?: string | undefined;
/** Optional key for form state reset. When the key changes, the form resets to its initial state. */
key?: string | undefined;
/** Server-side submission result for form state synchronization. */
lastResult?: SubmissionResult<ErrorShape> | null | undefined;
/** Form submission handler called when the form is submitted with no validation errors. */
onSubmit?: SubmitHandler<FormShape, NoInfer<ErrorShape>, NoInfer<Value>> | undefined;
/** Initial form values. Can be a partial object matching your form structure. */
defaultValue?: DefaultValue<FormShape> | undefined;
/** HTML validation attributes for fields (required, minLength, pattern, etc.). */
constraint?: Record<string, ValidationAttributes> | undefined;
/**
* Determines when validation should run for the first time on a field.
* Overrides the global default set by FormOptionsProvider if provided.
*
* @default Inherits from FormOptionsProvider, or "onSubmit" if not configured
*/
shouldValidate?: 'onSubmit' | 'onBlur' | 'onInput' | undefined;
/**
* Determines when validation should run again after the field has been validated once.
* Overrides the global default set by FormOptionsProvider if provided.
*
* @default Inherits from FormOptionsProvider, or same as shouldValidate
*/
shouldRevalidate?: 'onSubmit' | 'onBlur' | 'onInput' | undefined;
/** Error handling callback triggered when validation errors occur. By default, it focuses the first invalid field. */
onError?: ErrorHandler<NoInfer<ErrorShape>> | undefined;
/** Input event handler for custom input event logic. */
onInput?: InputHandler | undefined;
/** Blur event handler for custom focus handling logic. */
onBlur?: BlurHandler | undefined;
/** Custom validation handler. Can be skipped if using the schema property, or combined with schema to customize validation errors. */
onValidate?: ValidateHandler<ErrorShape, Value, InferOutput<Schema>> | undefined;
};
export type FormOptions<FormShape extends Record<string, any> = Record<string, any>, ErrorShape extends BaseErrorShape = string extends BaseErrorShape ? string : BaseErrorShape, Value = undefined, Schema = undefined, RequiredKeys extends keyof BaseFormOptions<FormShape, ErrorShape, Value, Schema> = never> = RequireKey<BaseFormOptions<FormShape, ErrorShape, Value, Schema>, RequiredKeys>;
export interface FormContext<ErrorShape extends BaseErrorShape = DefaultErrorShape> {
/** The form's unique identifier */
formId: string;
/** Internal form state with validation results and field data */
state: FormState<ErrorShape>;
/** HTML validation attributes for fields */
constraint: Record<string, ValidationAttributes> | null;
/** Form submission event handler */
handleSubmit: (event: React.FormEvent<HTMLFormElement>) => void;
/** Input event handler for form-wide input events */
handleInput: (event: React.FormEvent) => void;
/** Blur event handler for form-wide blur events */
handleBlur: (event: React.FocusEvent) => void;
}
export type UnknownIntent = {
type: string;
payload?: unknown;
};
export type UnknownArgs<Args extends any[]> = {
[Key in keyof Args]: unknown;
};
export interface IntentDispatcher<FormShape extends Record<string, any> = Record<string, any>> {
/**
* Validate the whole form or a specific field?
*/
validate(name?: string): void;
/**
* Reset the form to a specific default value.
*
* @param options.defaultValue - The value to reset the form to. Pass `null` to clear all fields, or omit to reset to the initial default value from `useForm`.
*
* @example
* ```tsx
* // Reset to initial default value
* intent.reset()
*
* // Clear all fields
* intent.reset({ defaultValue: null })
*
* // Restore to a specific snapshot
* intent.reset({ defaultValue: snapshotValue })
* ```
*/
reset(options?: {
/**
* The value to reset the form to. If not provided, resets to the default value from `useForm`. Pass `null` to clear all fields instead.
*/
defaultValue?: DefaultValue<FormShape>;
}): void;
/**
* Update a field or a fieldset.
* If you provide a fieldset name, it will update all fields within that fieldset
*/
update<FieldShape = FormShape>(options: {
/**
* The name of the field. If you provide a fieldset name, it will update all fields within that fieldset.
*/
name?: FieldName<FieldShape>;
/**
* Specify the index of the item to update if the field is an array.
*/
index?: undefined;
/**
* The new value for the field or fieldset.
*/
value: DefaultValue<FieldShape>;
} | {
/**
* The name of the field. If you provide a fieldset name, it will update all fields within that fieldset.
*/
name: FieldName<FieldShape>;
/**
* Specify the index of the item to update if the field is an array.
*/
index: number;
/**
* The new value for the field or fieldset.
* When index is specified, this should be the item type, not the array type.
*/
value: unknown extends FieldShape ? any : FieldShape extends Array<infer ItemShape> ? ItemShape : any;
}): void;
/**
* Insert a new item into an array field.
*/
insert<FieldShape extends Array<any>>(options: {
/**
* The name of the array field to insert into.
*/
name: FieldName<FieldShape>;
/**
* The index at which to insert the new item.
* If not provided, it will be added to the end of the array.
*/
index?: number;
/**
* The default value for the new item.
*/
defaultValue?: FieldShape extends Array<infer ItemShape> ? DefaultValue<ItemShape> : never;
}): void;
/**
* Remove an item from an array field.
*/
remove(options: {
/**
* The name of the array field to remove from.
*/
name: FieldName<Array<any>>;
/**
* The index of the item to remove.
*/
index: number;
}): void;
/**
* Reorder items in an array field.
*/
reorder(options: {
name: FieldName<Array<any>>;
from: number;
to: number;
}): void;
}
export type FormIntent<Dispatcher extends IntentDispatcher = IntentDispatcher> = {
[Type in keyof Dispatcher]: Dispatcher[Type] extends (...args: infer Args) => void ? {
type: Type;
payload: Args extends [infer Payload] ? Payload : undefined;
} : never;
}[keyof Dispatcher];
export type ActionHandler<Signature extends (payload: any) => void = (payload: any) => void> = {
validatePayload?(...args: UnknownArgs<Parameters<Signature>>): boolean;
onApply?(value: Record<string, FormValue>, ...args: Parameters<Signature>): Record<string, FormValue> | undefined;
onUpdate?<ErrorShape extends BaseErrorShape>(state: FormState<ErrorShape>, action: FormAction<ErrorShape, {
type: string;
payload: Signature extends (payload: infer Payload) => void ? Payload : undefined;
}, {
reset: (defaultValue?: Record<string, unknown> | null) => FormState<ErrorShape>;
}>): FormState<ErrorShape>;
};
type BaseCombine<T, K extends PropertyKey = T extends unknown ? keyof T : never> = T extends unknown ? T & Partial<Record<Exclude<K, keyof T>, never>> : never;
export type Combine<T> = {
[K in keyof BaseCombine<T>]: BaseCombine<T>[K];
};
/**
* Extend this interface to define the base error shape for validation.
*
* @example
* ```ts
* declare module '@conform-to/react/future' {
* interface CustomTypes {
* errorShape: { message: string; code: string };
* }
* }
* ```
*/
export interface CustomTypes {
}
export type BaseErrorShape = CustomTypes extends {
errorShape: infer Shape;
} ? Shape : unknown;
export type DefaultErrorShape = CustomTypes extends {
errorShape: infer Shape;
} ? Shape : string;
/** Base field metadata object containing field state, validation attributes, and accessibility IDs. */
export type BaseMetadata<FieldShape, ErrorShape extends BaseErrorShape> = ValidationAttributes & {
/** Unique key for React list rendering (for array fields). */
key: string | undefined;
/** The field name path exactly as provided. */
name: FieldName<FieldShape>;
/** The field's unique identifier, automatically generated as {formId}-field-{fieldName}. */
id: string;
/** Auto-generated ID for associating field descriptions via aria-describedby. */
descriptionId: string;
/** Auto-generated ID for associating field errors via aria-describedby. */
errorId: string;
/** The form's unique identifier for associating field via the `form` attribute. */
formId: string;
/**
* The field's default value as a string.
*
* Returns an empty string `''` when:
* - No default value is set (field value is `null` or `undefined`)
* - The field value cannot be serialized to a string (e.g., objects or arrays)
*/
defaultValue: string;
/**
* Default selected options for multi-select fields or checkbox group.
*
* Returns an empty array `[]` when:
* - No default options are set (field value is `null` or `undefined`)
* - The field value cannot be serialized to a string array (e.g., nested objects or arrays of objects)
*/
defaultOptions: string[];
/**
* Default checked state for checkbox inputs. Returns `true` if the field value is `'on'`.
*
* For radio buttons, compare the field's `defaultValue` with the radio button's value attribute instead.
*/
defaultChecked: boolean;
/** Whether this field has been touched (through intent.validate() or the shouldValidate option). */
touched: boolean;
/** Whether this field currently has no validation errors. */
valid: boolean;
/** @deprecated Use `.valid` instead. This was not an intentionl breaking change and would be removed in the next minor version soon */
invalid: boolean;
/** Array of validation error messages for this field. */
errors: ErrorShape[] | undefined;
/** Object containing errors for all touched subfields. */
fieldErrors: Record<string, ErrorShape[]>;
/** Boolean value for the `aria-invalid` attribute. Indicates whether the field has validation errors for screen readers. */
ariaInvalid: boolean | undefined;
/** String value for the `aria-describedby` attribute. Contains the errorId when invalid, undefined otherwise. Merge with descriptionId manually if needed (e.g. `${metadata.descriptionId} ${metadata.ariaDescribedBy}`). */
ariaDescribedBy: string | undefined;
/** Method to get nested fieldset for object fields under this field. */
getFieldset<FieldsetShape = keyof NonNullable<FieldShape> extends never ? unknown : FieldShape>(): Fieldset<FieldsetShape, ErrorShape>;
/** Method to get array of fields for list/array fields under this field. */
getFieldList<FieldItemShape = [FieldShape] extends [
Array<infer ItemShape> | null | undefined
] ? ItemShape : unknown>(): Array<FieldMetadata<FieldItemShape, ErrorShape>>;
};
export type SatisfyComponentProps<ElementType extends React.ElementType, CustomProps extends React.ComponentPropsWithoutRef<ElementType>> = CustomProps;
/**
* Interface for extending field metadata with additional properties.
*/
export interface CustomMetadata<FieldShape = any, ErrorShape extends BaseErrorShape = DefaultErrorShape> {
}
export type CustomMetadataDefinition = <FieldShape, ErrorShape extends BaseErrorShape>(metadata: BaseMetadata<FieldShape, ErrorShape>) => keyof CustomMetadata<FieldShape, ErrorShape> extends never ? {} : CustomMetadata<any, any>;
/** Field metadata object containing field state, validation attributes, and nested field access methods. */
export type FieldMetadata<FieldShape, ErrorShape extends BaseErrorShape = DefaultErrorShape> = Readonly<(keyof CustomMetadata<FieldShape, ErrorShape> extends never ? {} : CustomMetadata<FieldShape, ErrorShape>) & BaseMetadata<FieldShape, ErrorShape>>;
/** Fieldset object containing all form fields as properties with their respective field metadata. */
export type Fieldset<FieldShape, ErrorShape extends BaseErrorShape = DefaultErrorShape> = {
[Key in keyof Combine<FieldShape>]-?: FieldMetadata<Combine<FieldShape>[Key], ErrorShape>;
};
/** Form-level metadata and state object containing validation status, errors, and field access methods. */
export type FormMetadata<ErrorShape extends BaseErrorShape = DefaultErrorShape> = Readonly<{
/** Unique identifier that changes on form reset */
key: string;
/** The form's unique identifier. */
id: string;
/** Auto-generated ID for associating form descriptions via aria-describedby. */
descriptionId: string;
/** Auto-generated ID for associating form errors via aria-describedby. */
errorId: string;
/** Whether any field in the form has been touched (through intent.validate() or the shouldValidate option). */
touched: boolean;
/** Whether the form currently has no validation errors. */
valid: boolean;
/** @deprecated Use `.valid` instead. This was not an intentional breaking change and would be removed in the next minor version soon */
invalid: boolean;
/** Form-level validation errors, if any exist. */
errors: ErrorShape[] | undefined;
/** Object containing errors for all touched fields. */
fieldErrors: Record<string, ErrorShape[]>;
/** The form's initial default values. */
defaultValue: Record<string, unknown>;
/** Form props object for spreading onto the <form> element. */
props: Readonly<{
id: string;
onSubmit: React.FormEventHandler<HTMLFormElement>;
onBlur: React.FocusEventHandler<HTMLFormElement>;
onInput: React.FormEventHandler<HTMLFormElement>;
noValidate: boolean;
}>;
/** The current state of the form */
context: FormContext<ErrorShape>;
/** Method to get metadata for a specific field by name. */
getField<FieldShape>(name: FieldName<FieldShape>): FieldMetadata<FieldShape, ErrorShape>;
/** Method to get a fieldset object for nested object fields. */
getFieldset<FieldShape>(name?: FieldName<FieldShape>): Fieldset<keyof NonNullable<FieldShape> extends never ? unknown : FieldShape, ErrorShape>;
/** Method to get an array of field objects for array fields. */
getFieldList<FieldShape>(name: FieldName<FieldShape>): Array<FieldMetadata<[
FieldShape
] extends [Array<infer ItemShape> | null | undefined] ? ItemShape : unknown, ErrorShape>>;
}>;
export type ValidateResult<ErrorShape, Value> = FormError<ErrorShape> | null | {
error: FormError<ErrorShape> | null;
value?: Value;
};
export type ValidateContext<SchemaValue> = {
/**
* The submitted values mapped by field name.
* Supports nested names like `user.email` and indexed names like `items[0].id`.
*/
payload: Record<string, FormValue>;
/**
* Form error object. Initially empty, but populated with schema validation
* errors when a schema is provided and validation fails.
*/
error: FormError<string>;
/**
* The submission intent derived from the button that triggered the form submission.
*/
intent: UnknownIntent | null;
/**
* The raw FormData object of the submission.
*/
formData: FormData;
/**
* Reference to the HTML form element that triggered the submission.
*/
formElement: HTMLFormElement;
/**
* The specific element (button/input) that triggered the form submission.
*/
submitter: HTMLElement | null;
/**
* The validated value from schema validation. Only defined when a schema is provided
* and the validation succeeds. Undefined if no schema is provided or validation fails.
*/
schemaValue: SchemaValue;
};
export type ValidateHandler<ErrorShape, Value, SchemaValue = undefined> = (ctx: ValidateContext<SchemaValue>) => ValidateResult<ErrorShape, Value> | Promise<ValidateResult<ErrorShape, Value>> | [
ValidateResult<ErrorShape, Value> | undefined,
Promise<ValidateResult<ErrorShape, Value>> | undefined
] | undefined;
export interface FormInputEvent extends React.FormEvent<HTMLFormElement> {
currentTarget: EventTarget & HTMLFormElement;
target: EventTarget & (HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement);
}
export interface FormFocusEvent extends React.FormEvent<HTMLFormElement> {
currentTarget: EventTarget & HTMLFormElement;
relatedTarget: EventTarget | null;
target: EventTarget & (HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement);
}
export type ErrorContext<ErrorShape> = {
formElement: HTMLFormElement;
error: FormError<ErrorShape>;
intent: UnknownIntent | null;
};
export type ErrorHandler<ErrorShape> = (ctx: ErrorContext<ErrorShape>) => void;
export type InputHandler = (event: FormInputEvent) => void;
export type BlurHandler = (event: FormFocusEvent) => void;
export type SubmitContext<FormShape extends Record<string, any> = Record<string, any>, ErrorShape extends BaseErrorShape = DefaultErrorShape, Value = undefined> = {
formData: FormData;
value: Value;
update: (options: {
error?: Partial<FormError<ErrorShape>> | null | undefined;
value?: FormShape | null | undefined;
reset?: boolean | undefined;
}) => void;
};
export type SubmitHandler<FormShape extends Record<string, any> = Record<string, any>, ErrorShape extends BaseErrorShape = DefaultErrorShape, Value = undefined> = (event: React.FormEvent<HTMLFormElement>, ctx: SubmitContext<FormShape, ErrorShape, Value>) => void | Promise<void>;
export {};
//# sourceMappingURL=types.d.ts.map