remix-validated-form
Version:
Form component and utils for easy form validation in remix
365 lines (354 loc) • 13.3 kB
TypeScript
import * as _remix_run_server_runtime from '@remix-run/server-runtime';
import { FetcherWithComponents, Form } from '@remix-run/react';
import React, { ComponentProps } from 'react';
type ValidationBehavior = "onBlur" | "onChange" | "onSubmit";
type ValidationBehaviorOptions = {
initial: ValidationBehavior;
whenTouched: ValidationBehavior;
whenSubmitted: ValidationBehavior;
};
type HandledProps = "name" | "defaultValue" | "defaultChecked";
type Callbacks = "onChange" | "onBlur";
type MinimalInputProps = {
onChange?: ((...args: any[]) => void) | undefined;
onBlur?: ((...args: any[]) => void) | undefined;
defaultValue?: any;
defaultChecked?: boolean | undefined;
name?: string | undefined;
type?: string | undefined;
};
type GetInputProps = <T extends MinimalInputProps>(props?: Omit<T, HandledProps | Callbacks> & Partial<Pick<T, Callbacks>>) => T;
/**
* Returns whether or not the parent form is currently being submitted.
* This is different from Remix's `useNavigation()` in that it
* is aware of what form it's in and when _that_ form is being submitted.
*
* @param formId
*/
declare const useIsSubmitting: (formId?: string) => boolean;
/**
* Returns whether or not the current form is valid.
*
* @param formId the id of the form. Only necessary if being used outside a ValidatedForm.
*/
declare const useIsValid: (formId?: string) => boolean;
type FieldProps = {
/**
* The validation error message if there is one.
*/
error?: string;
/**
* Clears the error message.
*/
clearError: () => void;
/**
* Validates the field.
*/
validate: () => void;
/**
* The default value of the field, if there is one.
*/
defaultValue?: any;
/**
* Whether or not the field has been touched.
*/
touched: boolean;
/**
* Helper to set the touched state of the field.
*/
setTouched: (touched: boolean) => void;
/**
* Helper to get all the props necessary for a regular input.
*/
getInputProps: GetInputProps;
};
/**
* Provides the data and helpers necessary to set up a field.
*/
declare const useField: (name: string, options?: {
/**
* Allows you to configure a custom function that will be called
* when the input needs to receive focus due to a validation error.
* This is useful for custom components that use a hidden input.
*/
handleReceiveFocus?: () => void;
/**
* Allows you to specify when a field gets validated (when using getInputProps)
*/
validationBehavior?: Partial<ValidationBehaviorOptions>;
/**
* The formId of the form you want to use.
* This is not necesary if the input is used inside a form.
*/
formId?: string;
}) => FieldProps;
declare const useControlField: <T>(name: string, formId?: string) => readonly [T, (value: T) => void];
declare const useUpdateControlledField: (formId?: string) => (field: string, value: unknown) => void;
declare const FORM_DEFAULTS_FIELD: "__rvfInternalFormDefaults";
type FieldErrors = Record<string, string>;
type TouchedFields = Record<string, boolean>;
type GenericObject = {
[key: string]: any;
};
type ValidatorError = {
subaction?: string;
formId?: string;
fieldErrors: FieldErrors;
};
type ValidationErrorResponseData = {
subaction?: string;
formId?: string;
fieldErrors: FieldErrors;
repopulateFields?: unknown;
};
type BaseResult = {
submittedData: GenericObject;
formId?: string;
};
type ErrorResult = BaseResult & {
error: ValidatorError;
data: undefined;
};
type SuccessResult<DataType> = BaseResult & {
data: DataType;
error: undefined;
};
/**
* The result when validating a form.
*/
type ValidationResult<DataType> = SuccessResult<DataType> | ErrorResult;
/**
* The result when validating an individual field in a form.
*/
type ValidateFieldResult = {
error?: string;
};
/**
* A `Validator` can be passed to the `validator` prop of a `ValidatedForm`.
*/
type Validator<DataType> = {
validate: (unvalidatedData: GenericObject) => Promise<ValidationResult<DataType>>;
/**
* @deprecated Will be removed in a future version of remix-validated-form
*/
validateField?: (unvalidatedData: GenericObject, field: string) => Promise<ValidateFieldResult>;
};
type Valid<DataType> = {
data: DataType;
error: undefined;
};
type Invalid = {
error: FieldErrors;
data: undefined;
};
type CreateValidatorArg<DataType> = {
validate: (unvalidatedData: GenericObject) => Promise<Valid<DataType> | Invalid>;
validateField: (unvalidatedData: GenericObject, field: string) => Promise<ValidateFieldResult>;
};
type ValidatorData<T extends Validator<any>> = T extends Validator<infer U> ? U : never;
/**
* Takes the errors from a `Validator` and returns a `Response`.
* When you return this from your action, `ValidatedForm` on the frontend will automatically
* display the errors on the correct fields on the correct form.
*
* You can also provide a second argument to `validationError`
* to specify how to repopulate the form when JS is disabled.
*
* @example
* ```ts
* const result = validator.validate(await request.formData());
* if (result.error) return validationError(result.error, result.submittedData);
* ```
*/
declare function validationError(error: ValidatorError, repopulateFields?: unknown, init?: ResponseInit): _remix_run_server_runtime.TypedResponse<ValidationErrorResponseData>;
type FormDefaults = {
[formDefaultsKey: `${typeof FORM_DEFAULTS_FIELD}_${string}`]: any;
};
type internal_FORM_DEFAULTS_FIELD = typeof FORM_DEFAULTS_FIELD;
declare const setFormDefaults: <DataType = any>(formId: string, defaultValues: Partial<DataType>) => FormDefaults;
type DeepPartial<T> = T extends object ? {
[P in keyof T]?: DeepPartial<T[P]>;
} : T;
type SubactionData<DataType, Subaction extends string | undefined> = DataType & {
subaction: Subaction;
};
type DataForSubaction<DataType, Subaction extends string | undefined> = Subaction extends string ? SubactionData<DataType, Subaction> extends undefined ? DataType : SubactionData<DataType, Subaction> : DataType;
type FormProps<DataType, Subaction extends string | undefined> = {
/**
* A `Validator` object that describes how to validate the form.
*/
validator: Validator<DataType>;
/**
* A submit callback that gets called when the form is submitted
* after all validations have been run.
*/
onSubmit?: (data: DataForSubaction<DataType, Subaction>, event: React.FormEvent<HTMLFormElement>) => void | Promise<void>;
/**
* Allows you to provide a `fetcher` from Remix's `useFetcher` hook.
* The form will use the fetcher for loading states, action data, etc
* instead of the default form action.
*/
fetcher?: FetcherWithComponents<any>;
/**
* Accepts an object of default values for the form
* that will automatically be propagated to the form fields via `useField`.
*/
defaultValues?: DeepPartial<DataForSubaction<DataType, Subaction>>;
/**
* A ref to the form element.
*/
formRef?: React.RefObject<HTMLFormElement>;
/**
* An optional sub-action to use for the form.
* Setting a value here will cause the form to be submitted with an extra `subaction` value.
* This can be useful when there are multiple forms on the screen handled by the same action.
*/
subaction?: Subaction;
/**
* Reset the form to the default values after the form has been successfully submitted.
* This is useful if you want to submit the same form multiple times,
* and don't redirect in-between submissions.
*/
resetAfterSubmit?: boolean;
/**
* Normally, the first invalid input will be focused when the validation fails on form submit.
* Set this to `false` to disable this behavior.
*/
disableFocusOnError?: boolean;
} & Omit<ComponentProps<typeof Form>, "onSubmit">;
/**
* The primary form component of `remix-validated-form`.
*/
declare function ValidatedForm<DataType extends {
[fieldName: string]: any;
}, Subaction extends string | undefined>({ validator, onSubmit, children, fetcher, action, defaultValues: unMemoizedDefaults, formRef: formRefProp, onReset, subaction, resetAfterSubmit, disableFocusOnError, method, replace, id, preventScrollReset, relative, encType, ...rest }: FormProps<DataType, Subaction>): JSX.Element;
/**
* Used to create a validator for a form.
* It provides built-in handling for unflattening nested objects and
* extracting the values from FormData.
*/
declare function createValidator<T>(validator: CreateValidatorArg<T>): Validator<T>;
type FormContextValue = {
/**
* All the errors in all the fields in the form.
*/
fieldErrors: FieldErrors;
/**
* Clear the errors of the specified fields.
*/
clearError: (...names: string[]) => void;
/**
* Validate the specified field.
*/
validateField: (fieldName: string) => Promise<string | null>;
/**
* The `action` prop of the form.
*/
action?: string;
/**
* The `subaction` prop of the form.
*/
subaction?: string;
/**
* Whether or not the form is submitting.
*/
isSubmitting: boolean;
/**
* Whether or not a submission has been attempted.
* This is true once the form has been submitted, even if there were validation errors.
* Resets to false when the form is reset.
*/
hasBeenSubmitted: boolean;
/**
* Whether or not the form is valid.
*/
isValid: boolean;
/**
* The default values of the form.
*/
defaultValues?: {
[fieldName: string]: any;
};
/**
* Register a custom focus handler to be used when
* the field needs to receive focus due to a validation error.
*/
registerReceiveFocus: (fieldName: string, handler: () => void) => () => void;
/**
* Any fields that have been touched by the user.
*/
touchedFields: TouchedFields;
/**
* Change the touched state of the specified field.
*/
setFieldTouched: (fieldName: string, touched: boolean) => void;
/**
* Validate the whole form and populate any errors.
*/
validate: () => Promise<ValidationResult<unknown>>;
/**
* Clears all errors on the form.
*/
clearAllErrors: () => void;
/**
* Resets the form.
*
* _Note_: The equivalent behavior can be achieved by calling formElement.reset()
* or clicking a button element with `type="reset"`.
*/
reset: () => void;
/**
* Submits the form, running all validations first.
*
* _Note_: This is equivalent to clicking a button element with `type="submit"` or calling formElement.submit().
*/
submit: () => void;
/**
* Returns the current form values as FormData
*/
getValues: () => FormData;
};
/**
* Provides access to some of the internal state of the form.
*/
declare const useFormContext: (formId?: string) => FormContextValue;
type FieldArrayValidationBehavior = "onChange" | "onSubmit";
type FieldArrayValidationBehaviorOptions = {
initial: FieldArrayValidationBehavior;
whenSubmitted: FieldArrayValidationBehavior;
};
type FieldArrayItem<T> = {
/**
* The default value of the item.
* This does not update as the field is changed by the user.
*/
defaultValue: T;
/**
* A unique key for the item.
* Use this as the key prop when rendering the item.
*/
key: string;
};
type FieldArrayHelpers<Item = any> = {
push: (item: Item) => void;
swap: (indexA: number, indexB: number) => void;
move: (from: number, to: number) => void;
insert: (index: number, value: Item) => void;
unshift: (value: Item) => void;
remove: (index: number) => void;
pop: () => void;
replace: (index: number, value: Item) => void;
};
type UseFieldArrayOptions = {
formId?: string;
validationBehavior?: Partial<FieldArrayValidationBehaviorOptions>;
};
declare function useFieldArray<Item = any>(name: string, { formId, validationBehavior }?: UseFieldArrayOptions): [items: FieldArrayItem<Item>[], helpers: FieldArrayHelpers<any>, error: string | undefined];
type FieldArrayProps<Item> = {
name: string;
children: (items: FieldArrayItem<Item>[], helpers: FieldArrayHelpers<Item>, error: string | undefined) => React.ReactNode;
formId?: string;
validationBehavior?: FieldArrayValidationBehaviorOptions;
};
declare function FieldArray<Item = any>({ name, children, formId, validationBehavior, }: FieldArrayProps<Item>): JSX.Element;
export { BaseResult, CreateValidatorArg, ErrorResult, FieldArray, FieldArrayHelpers, FieldArrayProps, FieldErrors, FieldProps, FormContextValue, FormDefaults, FormProps, GenericObject, Invalid, SuccessResult, TouchedFields, Valid, ValidateFieldResult, ValidatedForm, ValidationErrorResponseData, ValidationResult, Validator, ValidatorData, ValidatorError, createValidator, internal_FORM_DEFAULTS_FIELD, setFormDefaults, useControlField, useField, useFieldArray, useFormContext, useIsSubmitting, useIsValid, useUpdateControlledField, validationError };