form-signals Documentation

website·Indexed 30 days ago

https://gutentag2012.github.io/form-signals

A reactive form management library utilizing signals to handle form state, validation, and field groups. It provides a core logic layer and dedicated bindings for React and React Native, featuring APIs such as FormLogic, FieldLogic, and hooks like useForm and useField. The library currently supports zod for validation and includes a FormDevTools component for debugging form state.

Tokens
22.4K
Snippets
116
Records
131
Agent score
74%

What's inside form-signals

  1. Understand Signals in Form Signals

    Signals are reactive values used to trigger UI updates or calculate computed data. Form Signals is built on top of the Preact Signals API. Unlike traditional state management, Signals minimize re-renders by only updating components specifically subscribed to the Signal that changed. This is particularly beneficial for complex forms with many fields, ensuring a smoother user experience by avoiding global re-renders.
  2. Manage form state with FormLogic

    The FormLogic class serves as the single source of truth for all form data. It manages data storage, handles form submission, and performs global validation. Nested data is stored using nested signals to ensure that updates only trigger subscribers of the specific child value being changed. To avoid unnecessary subscriptions, use the .peek() method instead of the .value property when you do not need to react to changes.
    import {FormLogic} from "./FormLogic";
    
    const form = new FormLogic({
      defaultValues: {
        nested: {
          thing: 1
        }
      }
    });
    
    // Access a nested value (subscribes to changes)
    const nestedValue = form.data.value.nested.thing.value;
  3. Handle form submission and server-side errors

    Define an onSubmit callback in the FormLogic options. This callback is triggered when form.handleSubmit() is called, provided the form is valid. To handle server-side validation errors, use the addErrors function provided in the onSubmit arguments. Errors must be a map of field names to error messages; use an empty string '' as the key for form-level errors.
    const form = new FormLogic<FormValues>({
      onSubmit: async (values, addErrors) => {
        const response = await fetch("https://api.example.com", {
          method: "POST",
          body: JSON.stringify(values)
        });
        const data = await response.json();
        if (!response.ok) {
          // data should be e.g., { "name": "Name is required" }
          addErrors(data);
        }
      }
    });
    
    // Trigger submission
    form.handleSubmit();
  4. Integrate async data with FormLogic

    Since FormLogic does not have built-in support for asynchronous default values, you should initialize the form in a disabled state and use the updateOptions method to apply the fetched data as defaultValues and re-enable the form once the promise resolves.
    import { FormLogic } from '@formsignals/form-core';
    
    type FormValues = {
      name: string;
      age: number;
    };
    
    // Initialize form as disabled while waiting for data
    const form = new FormLogic<FormValues>({
      disabled: true
    });
    
    fetchData().then((data) => {
      // Update default values and enable the form after data is fetched
      form.updateOptions({
        defaultValues: data,
        disabled: false
      });
    });
  5. Create a FieldGroupLogic instance

    A FieldGroupLogic acts as a container for a set of fields associated with a form. While you can instantiate it via the constructor, the recommended approach is to use the getOrCreateFieldGroup method on a FormLogic instance.
    import { FormLogic, FieldGroupLogic } from '@formsignals/form-core';
    
    type FormValues = {
      name: string;
    };
    const form = new FormLogic<FormValues>();
    
    // Recommended: Create a new field group through the form
    const field = form.getOrCreateFieldGroup(['name']);
    
    // Alternative: Manually create a new field group
    const fieldManual = new FieldGroupLogic(form, ['name']);
  6. Use Fields to wrap form data

    A Field acts as a wrapper for a single piece of data within a FormLogic instance. It manages field-specific state, validation, and transformation. Fields do not store data directly but hold a reference to the data in the form. By default, when a field is unmounted/removed, the referenced data is also removed from the form.
  7. Initialize a FormLogic instance

    The FormLogic class is the central source of truth for form values in the library. You create a new instance by calling new FormLogic(). It supports two generic types: TData (the type of the form values) and TAdapter (the type of the validation adapter, which defaults to undefined).
    import {FormLogic} from '@formsignals/form-core';
    
    const form = new FormLogic();
  8. Initialize a FormLogic instance

    Create a form using FormLogic. You can either pass a TypeScript type parameter to define the shape of the form values or provide default values in the constructor to let TypeScript infer the type.
    import {FormLogic} from "@formsignals/form-core";
    
    // Option 1: Using a type parameter
    type FormValues = { name: string; }
    const form = new FormLogic<FormValues>();
    
    // Option 2: Using default values for type inference
    const form = new FormLogic({
      name: "John Doe"
    });
  9. Access and manage array data in forms

    Arrays in form-signals are handled as nested fields where every value is wrapped in a signal. To support UI library requirements for unique identifiers (tracking additions, removals, or moves), the library automatically adds a unique key identifier to each array signal. Because of this structure, raw array item data must be accessed via the .data property before accessing the .value of the signal.
    import {FormLogic} from "./FormLogic";
    
    const form = new FormLogic({
      defaultValues: {
        array: [1],
        object: {key: 1}
      }
    });
    
    // Access an object's value
    const objectValue = form.data.value.object.value.key.value;
    
    // Access an array item's unique key
    const arrayKey = form.data.value.array.data[0].key;
    
    // Access an array item's actual value
    const arrayValue = form.data.value.array.data[0].data.value;
  10. Create and mount form fields

    Use getOrCreateField to add a field to the form. Fields must be mounted using .mount() before their handlers or validation can be used. Once mounted, you can update the field value via the handleChange method or by directly modifying the data.value signal.
    const field = form.getOrCreateField("name");
    
    // Mounting is required for handlers and validation
    form.mount();
    field.mount();
    
    // Update value via handler
    field.handleChange("New Name");
    
    // Update value via signal
    field.data.value = "New Name";
  11. Create a field group using the useFieldGroup hook

    The useFieldGroup hook allows you to create a field group programmatically. It wraps the core library's form.getOrCreateFieldGroup method and accepts the form instance, an array of field names (members), and an options object (such as a validator). The hook returns a fieldGroup object containing a FieldGroupProvider and a handleSubmit method.
    import {useForm, useFieldGroup} from "@formsignals/form-react";
    
    export default function MyForm() {
      const form = useForm<FormValues>();
      const fieldGroup = useFieldGroup(form, ["field1", "field2"], {
        validator: (value) => {
          if (value.field1 > value.field2) {
            return "Field 1 must be smaller than Field 2";
          }
        },
      });
    
      return (
        <form.FormProvider>
          <fieldGroup.FieldGroupProvider>
            {/* Your form fields here */}
            <ErrorText />
            <button type="button" onClick={fieldGroup.handleSubmit}>
              Check this Part
            </button>
          </fieldGroup.FieldGroupProvider>
        </form.FormProvider>
      )
    }