UNPKG

remix-validated-form

Version:

Form component and utils for easy form validation in remix

365 lines (354 loc) 13.3 kB
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 };