Skip to content

FormLogic API Reference

The FormLogic is the center of this library, it is the single source of truth for the form values. Create a new instance of the FormLogic by calling new FormLogic().

ts
import {FormLogic} from '@formsignals/form-core';

const form = new FormLogic()

Generic Types

The form takes two generic types, TData and TAdapter.

Generic TypeDescription
TDataThe type of the form values.
TAdapterThe type of the validation adapter. Defaults to undefined.

WARNING

If you are using an adapter and no default values, you need to pass the type of the adapter as well as the form values to the generic arguments of the form.

ts
import {FormLogic} from '@formsignals/form-core';
import {ZodAdapter} from '@formsignals/validation-adapter-zod';

type FormValues = { name: string };
const form = new FormLogic<FormValues, typeof ZodAdapter>({
  validatorAdapter: ZodAdapter,
});

FormLogicOptions

When creating a form, you can pass the following options:

ts
export type FormLogicOptions<
  TData,
  TAdapter extends ValidatorAdapter | undefined = undefined,
> = {
  disabled?: boolean

  validatorAdapter?: TAdapter

  validator?: TAdapter extends undefined
    ? ValidatorSync<TData>
    : ValidatorSync<TData> | ReturnType<ValidatorSchemaType<TData, never[]>>
  validatorOptions?: ValidatorOptions

  validatorAsync?: TAdapter extends undefined
    ? ValidatorAsync<TData>
    : ValidatorAsync<TData> | ReturnType<ValidatorSchemaType<TData, never[]>>
  validatorAsyncOptions?: ValidatorAsyncOptions

  defaultValues?: TData

  onSubmit?: (data: TData, addErrors: (errors: Partial<Record<Paths<TData>, ValidationError> | ValidationError>) => void) => unknown | Promise<unknown>
}
OptionDescription
disabledIf the form is disabled, it will not validate or submit.
validatorAdapterThe adapter that will be used to transform a given validator schema and run the validation on it.
Reference the Validation API.
validatorIf no adapter is given, it is a synchronous function that returns an error message. If an adapter is given, it can also be a validation schema fitting for that adapter.
Reference the Validation API.
validatorOptionsOptions to pass to the synchronous validation.
Reference the Validation API.
validatorAsyncIf no adapter is given, it is an asynchronous function that returns an error message. If an adapter is given, it can also be a validation schema fitting for that adapter.
Reference the Validation API.
validatorAsyncOptionsOptions to pass to the asynchronous validation.
Reference the Validation API.
defaultValuesThe default values for the form. They will be transformed to the nested signals and set as the form values.
onSubmitThe function that is called once the form is submitted without any validation errors. This function receives the TData as the input as well as a function to add errors to the form or fields during validation. It can be an asynchronous function.

Form State

You can access several states of the form, most of them being signals.

ts
interface FormLogic<
  TData,
  TAdapter extends ValidatorAdapter | undefined = undefined,
> {
  get data(): SignalifiedData<TData>

  getValueForPath<TPath extends Paths<TData>>(path: TPath): SignalifiedData<ValueAtPath<TData, TPath>>

  get json(): ReadonlySignal<TData>

  get isMounted(): ReadonlySignal<boolean>

  get isValidatingFields(): ReadonlySignal<boolean>

  get errors(): ReadonlySignal<Array<ValidationError>>

  get fieldErrors(): ReadonlySignal<Array<ValidationError>>

  get fields(): ReadonlySignal<Array<FieldLogic<TData, Paths<TData>, any>>>

  get fieldGroups(): ReadonlySignal<Array<FieldGroupLogic<TData, any>>>

  get isValidForm(): ReadonlySignal<boolean>

  get isValidFields(): ReadonlySignal<boolean>

  get isValidFieldGroups(): ReadonlySignal<boolean>

  get isValid(): ReadonlySignal<boolean>

  get isTouched(): ReadonlySignal<boolean>

  get isDirty(): ReadonlySignal<boolean>

  get submitCountSuccessful(): ReadonlySignal<number>

  get submitCountUnsuccessful(): ReadonlySignal<number>

  get submitCount(): ReadonlySignal<number>

  get isValidatingForm(): ReadonlySignal<boolean>

  get isValidatingFields(): ReadonlySignal<boolean>

  get isValidatingFieldGroups(): ReadonlySignal<boolean>

  get isValidating(): ReadonlySignal<boolean>

  get isSubmitting(): ReadonlySignal<boolean>

  get isSubmitted(): ReadonlySignal<boolean>

  get canSubmit(): ReadonlySignal<boolean>

  get disabled(): ReadonlySignal<boolean>

  get options(): ReadonlySignal<FormLogicOptions<TData, TAdapter> | undefined>
}
StateDescription
dataThe reactive signal of the form values. The returned signal can be written to.
getValueForPathGet the signal of a specific field in the form. The returned signal can be written to.
jsonThe signal of the form values as a plain JSON object. All signal references are resolved there.
isMountedIs the form currently mounted
errorsThe reactive signal of all errors in the form.
fieldErrorsThe reactive signal of all errors in the fields.
fieldsThe reactive signal of all fields in the form.
groupsThe reactive signal of all field groups in the form.
isValidFormIs the form valid?
isValidFieldsAre all fields valid?
isValidFieldGroupsAre all field groups valid?
isValidIs the form together with all its fields valid?
isTouchedIs the form touched? The form is touched if any of the fields got blurred.
isDirtyIs the form dirty? This property is calculated based on the current value and the default values. A form is dirty if those values are unequal using deep equality.
submitCountSuccessfulThe number of successful submits.
submitCountUnsuccessfulThe number of unsuccessful submits.
submitCountThe number of total submits.
isValidatingFieldsIs the form currently validating fields?
isValidatingFormIs the form currently validating?
isValidatingFieldGroupsIs the form currently validating field groups?
isValidatingIs the form currently validating fields or the form?
isSubmittingIs the form currently submitting?
isSubmittedHas the form been submitted?
canSubmitCan the form be submitted? It can only be submitted, if the form is not currently submitting, is valid, not currently validating and not disabled.
disabledIs the form disabled? When disabled the form will not validate or submit.
optionsThe options passed to the form. If the options get updated, this also reflects here.

Form Lifecycle Methods

The form has to be mounted and can be unmounted in several cases.

ts
interface FormLogic<
  TData,
  TAdapter extends ValidatorAdapter | undefined = undefined,
> {
  updateOptions(options?: FormLogicOptions<TData, TAdapter>): void

  mount(): Promise<() => void>

  unmount(): void

  setErrors(errors: Partial<ValidationErrorMap>): void

  disable(): void

  enable(): void
}
MethodInputDescription
updateOptionsThe new optionsUpdate the options of the form. This can be used to update the validation schema or the default values.
mount-Mount the form. This is necessary to start the validation and submit the form.
unmount-Unmount the form. This is necessary to stop the validation and submit the form.
setErrorsThe new errorsSet errors to the form. This can be used to add errors to the form that are not part of the validation. Existing errors will stay unless overwritten.
disable-Disable the form.
enable-Enable the form.

INFO

When using updateOptions to update the form default values, all form values that have not been changed will be set to the new default values.

Form Handlers

The form has several handlers that can be used to interact with the form.

ts
interface FormLogic<
  TData,
  TAdapter extends ValidatorAdapter | undefined = undefined,
> {
  validateForEvent(
    event: ValidatorEvents
  ): void | Promise<void>

  handleChange<TPath extends Paths<TData>>(
    path: TPath,
    newValue: ValueAtPath<TData, TPath>,
    options?: { shouldTouch?: boolean },
  ): void

  handleBlur(): Promise<void>

  handleSubmit(): Promise<void>
}
HandlerInputDescription
validateForEventThe event to validate forValidate the form for a specific event. This can be used to manually trigger a validation.
handleChangeThe path the the value; The value to update to; Additional options, whether the field at that path should get touchedChange the value of a specific field in the form.
handleBlur-Trigger the blur event on the form.
handleSubmit-Submit the form. This will validate the form and if it is valid, call the onSubmit function. If the onSubmit function is an asynchronous function, the form will be in the submitting state until the function is resolved.

Field & Field Group Helpers

The form offers several helpers to interact with fields and field groups

ts
interface FormLogic<
  TData,
  TAdapter extends ValidatorAdapter | undefined = undefined,
> {
  getOrCreateField<TPath extends Paths<TData>, TBoundValue = never, TFieldAdapter extends ValidatorAdapter | undefined = undefined, TMixin extends readonly Exclude<Paths<TData>, TPath>[] = never[]>(
    path: TPath,
    fieldOptions?: FieldLogicOptions<
      TData,
      TPath,
      TBoundValue,
      TFieldAdapter extends undefined ? TAdapter : TFieldAdapter,
      TMixin
    >,
  ): FieldLogic<TData, TPath, TBoundValue, TFieldAdapter, TAdapter, TMixin>

  getDefaultValueForPath<TPath extends Paths<TData>>(
    path: TPath,
  ): ValueAtPath<TData, TPath> | undefined

  getFieldForPath<TPath extends Paths<TData>, TBoundData = never, TFieldAdapter extends ValidatorAdapter | undefined = undefined, TMixin extends readonly Exclude<Paths<TData>, TPath>[] = never[]>(
    path: TPath,
  ): FieldLogic<TData, TPath, TBoundData, TFieldAdapter, TAdapter, TMixin>

  getOrCreateFieldGroup<
    TMembers extends Paths<TData>[],
    TGroupAdapter extends ValidatorAdapter | undefined,
    TMixin extends readonly ExcludeAll<Paths<TData>, TMembers>[] = never[],
  >(
    members: TMembers,
    options?: FieldGroupLogicOptions<
      TData,
      TMembers,
      TGroupAdapter extends undefined ? TAdapter : TGroupAdapter,
      TMixin
    >,
  ): FieldGroupLogic<TData, TMembers, TGroupAdapter, TAdapter, TMixin>
}
HelperInputDescription
getOrCreateFieldThe path to the value of the field; The options for the fieldGet or create a field in the form. If a field already exists, its options are getting updated with the passed options.
getDefaultValueForPathThe path to the value of the fieldGet the default value for a specific field in the form.
getFieldForPathThe path to the value of the fieldGet a specific field in the form.
getOrCreateFieldGroupThe members of the field group; The options for the field groupGet or create a field group in the form. If a field group already exists, its options are getting updated with the passed options.

Form Helpers

The form offers several helpers to interact with arrays and dynamic objects.

Some of these types might look complex, but they are necessary to ensure that you only can insert, swap or remove at the correct positions. E.g., you can only swap two values in an array if they are of the same type, or you can never insert into a tuple

ts
interface FormLogic<
  TData,
  TAdapter extends ValidatorAdapter | undefined = undefined,
> {
  setValueInObject<TPath extends Paths<TData>, TKey extends Paths<ValueAtPath<TData, TPath>>>(
    path: TPath,
    key: TKey,
    value: ValueAtPath<TData, ConnectPath<TPath, TKey>>,
    options?: { shouldTouch?: boolean },
  ): void

  removeValueInObject<TPath extends Paths<TData>, TKey extends Paths<ValueAtPath<TData, TPath>>>(
    path: TPath,
    key: KeepOptionalKeys<ValueAtPath<TData, TPath>, TKey>,
    options?: { shouldTouch?: boolean },
  ): void

  insertValueInArray<TName extends Paths<TData>, Index extends number>(
    name: TName,
    index: Index,
    value: ValueAtPath<TData, TName> extends any[]
      ? ValueAtPath<TData, TName>[number]
      : ValueAtPath<TData, TName> extends readonly any[]
        ? ValueAtPath<TData, TName>[Index]
        : never,
    options?: { shouldTouch?: boolean },
  ): void

  pushValueToArray<TName extends Paths<TData>>(
    name: TName,
    value: ValueAtPath<TData, TName> extends any[]
      ? ValueAtPath<TData, TName>[number]
      : never,
    options?: { shouldTouch?: boolean },
  ): void

  pushValueToArrayAtIndex<TName extends Paths<TData>>(
    name: TName,
    index: ValueAtPath<TData, TName> extends any[] ? number : never,
    value: ValueAtPath<TData, TName> extends any[]
      ? ValueAtPath<TData, TName>[number]
      : never,
    options?: { shouldTouch?: boolean },
  ): void

  removeValueFromArray<TName extends Paths<TData>>(
    name: TName,
    index: ValueAtPath<TData, TName> extends any[] ? number : never,
    options?: { shouldTouch?: boolean },
  ): void

  swapValuesInArray<TName extends Paths<TData>, IndexA extends number, IndexB extends number>(
    name: TName,
    indexA: ValueAtPath<TData, TName> extends any[]
      ? number
      : ValueAtPath<TData, TName> extends readonly any[]
        ? ValueAtPath<TData, TName>[IndexA] extends ValueAtPath<
            TData,
            TName
          >[IndexB]
          ? number
          : never
        : never,
    indexB: ValueAtPath<TData, TName> extends any[]
      ? number
      : ValueAtPath<TData, TName> extends readonly any[]
        ? ValueAtPath<TData, TName>[IndexB] extends ValueAtPath<
            TData,
            TName
          >[IndexA]
          ? number
          : never
        : never,
    options?: { shouldTouch?: boolean },
  ): void

  moveValueInArray<TName extends Paths<TData>, IndexA extends number, IndexB extends number>(
    name: TName,
    indexA: ValueAtPath<TData, TName> extends any[]
      ? number
      : ValueAtPath<TData, TName> extends readonly any[]
        ? ValueAtPath<TData, TName>[IndexA] extends ValueAtPath<
            TData,
            TName
          >[IndexB]
          ? number
          : never
        : never,
    indexB: ValueAtPath<TData, TName> extends any[]
      ? number
      : ValueAtPath<TData, TName> extends readonly any[]
        ? ValueAtPath<TData, TName>[IndexB] extends ValueAtPath<
            TData,
            TName
          >[IndexA]
          ? number
          : never
        : never,
    options?: { shouldTouch?: boolean },
  ): void
}
HelperInputDescription
setValueInObjectThe path to the object; The key to the value; The value to update to; Additional options, whether the field at that path should get touchedChange the value of a specific key in an object in the form.
removeValueInObjectThe path to the object; The key to the value; Additional options, whether the field at that path should get touchedRemove a specific key in an object in the form.
insertValueInArrayThe path to the array; The index to insert the value; The value to insert; Additional options, whether the field at that path should get touchedInsert a value at a specific index in an array in the form. This will override the value at that position.
pushValueToArrayThe path to the array; The value to push; Additional options, whether the field at that path should get touchedPush a value to the end of an array in the form.
pushValueToArrayAtIndexThe path to the array; The index to insert the value; The value to insert; Additional options, whether the field at that path should get touchedPush a value at a specific index in an array in the form. All indexes >= the wanted index are getting pushed one to the right.
removeValueFromArrayThe path to the array; The index to remove the value; Additional options, whether the field at that path should get touchedRemove a value at a specific index in an array in the form.
swapValuesInArrayThe path to the array; The index of the first value to swap; The index of the second value to swap; Additional options, whether the field at that path should get touchedSwap two values in an array in the form.
moveValueInArrayThe path to the array; The index of the value to move; The index to move the value to; Additional options, whether the field at that path should get touchedMove a value from one index to another in an array in the form.

Form Reset

The form can be reset to its default values and state.

ts
interface FormLogic<
  TData,
  TAdapter extends ValidatorAdapter | undefined = undefined,
> {
  resetStateForm(): void

  resetStateFields(): void

  resetState(): void

  resetValues(): void

  reset(): void
}
MethodDescription
resetStateFormReset the form state.
resetStateFieldsReset the fields state. This will call the reset method on each field.
resetStateReset the form state and the fields state.
resetValuesReset the form values to the default values.
resetReset the form state, the fields state and the form values to the default values.