Files
form/docs/guides/basic-concepts.md
Corbin Crutchley 54652ee674 feat: Add Yup and Zod validator support (#462)
* chore: first pass

* chore: onto something I think

* chore: closer but no cigar

* chore: infer validator

* chore: infer zod

* feat: add validation transformer logic

* chore: fix typings for react adapter

* chore: fix issue with `this` not being defined properly

* chore: mostly update FieldInfo types from Vue

* chore: work on fixing type inferencing

* fix: make ValidatorType optional

* chore: make TName restriction easier to grok

* chore: fix React types

* chore: fix Vue types

* chore: fix typing issues

* chore: fix various linting items

* chore: fix ESlint and validation logic

* chore: fix inferencing from formdata

* chore: fix form inferencing

* chore: fix React TS types to match form validator logic

* chore: fix Vue types

* chore: migrate zod validation to dedicated package

* chore: add first integration test for zod adapter

* chore: enable non-validator types to be passed to validator

* feat: add yup 1.x adapter

* chore: add functionality and tests for form-wide validators

* chore: fix typings of async validation types

* fix: async validation should now run as-expected more often

* chore: add async tests for Yup validator

* chore: rename packages to match naming schema better

* chore: add Zod examples for React and Vue

* chore: add React and Vue Yup support

* chore: fix formatting

* chore: fix CI types

* chore: initial work to drastically improve docs

* docs: improve docs for validation

* docs: add adapter validation docs
2023-10-18 02:22:05 -07:00

7.9 KiB

id, title
id title
basic-concepts Basic Concepts and Terminology

Basic Concepts and Terminology

This page introduces the basic concepts and terminology used in the @tanstack/react-form library. Familiarizing yourself with these concepts will help you better understand and work with the library.

Form Factory

The Form Factory is responsible for creating form instances with a shared configuration. It is created using the createFormFactory function, which accepts a configuration object with default values for the form fields. This shared configuration allows you to create multiple form instances with consistent behavior.

Example:

const formFactory = createFormFactory<Person>({
  defaultValues: {
    firstName: '',
    lastName: '',
    hobbies: [],
  },
})

Form Instance

A Form Instance is an object that represents an individual form and provides methods and properties for working with the form. You create a form instance using the useForm hook provided by the form factory. The hook accepts an object with an onSubmit function, which is called when the form is submitted.

const form = formFactory.useForm({
  onSubmit: async (values) => {
    // Do something with form data
    console.log(values)
  },
})

Field

A Field represents a single form input element, such as a text input or a checkbox. Fields are created using the form.Field component provided by the form instance. The component accepts a name prop, which should match a key in the form's default values. It also accepts a children prop, which is a render prop function that takes a field object as its argument.

Example:

<form.Field
  name="firstName"
  children={(field) => (
    <>
      <input
        value={field.state.value}
        onBlur={field.handleBlur}
        onChange={(e) => field.handleChange(e.target.value)}
      />
      <FieldInfo field={field} />
    </>
  )}
/>

Field State

Each field has its own state, which includes its current value, validation status, error messages, and other metadata. You can access a field's state using the field.state property.

Example:

const { value, error, touched, isValidating } = field.state

Field API

The Field API is an object passed to the render prop function when creating a field. It provides methods for working with the field's state.

Example:

<input
  value={field.state.value}
  onBlur={field.handleBlur}
  onChange={(e) => field.handleChange(e.target.value)}
/>

Validation

@tanstack/react-form provides both synchronous and asynchronous validation out of the box. Validation functions can be passed to the form.Field component using the validate and validateAsync props.

Example:

<form.Field
  name="firstName"
  validate={(value) => !value && 'A first name is required'}
  validateAsync={async (value) => {
    await new Promise((resolve) => setTimeout(resolve, 1000))
    return value.includes('error') && 'No "error" allowed in first name'
  }}
  children={(field) => (
    <>
      <input
        value={field.state.value}
        onBlur={field.handleBlur}
        onChange={(e) => field.handleChange(e.target.value)}
      />
      <FieldInfo field={field} />
    </>
  )}
/>

Validation Adapters

In addition to hand-rolled validation options, we also provide adapters like @tanstack/zod-form-adapter and @tanstack/yup-form-adapter to enable usage with common schema validation tools like Yup and Zod.

Example:

<form.Field
  name="firstName"
  onChange={z
    .string()
    .min(3, "First name must be at least 3 characters")}
  onChangeAsyncDebounceMs={500}
  onChangeAsync={z.string().refine(
    async (value) => {
      await new Promise((resolve) => setTimeout(resolve, 1000));
      return !value.includes("error");
    },
    {
      message: "No 'error' allowed in first name",
    },
  )}
/>

Reactivity

@tanstack/react-form offers various ways to subscribe to form and field state changes, such as the form.useStore hook, the form.Subscribe component, and the form.useField hook. These methods allow you to optimize your form's rendering performance by only updating components when necessary.

Example:

<form.Subscribe
  selector={(state) => [state.canSubmit, state.isSubmitting]}
  children={([canSubmit, isSubmitting]) => (
    <button type="submit" disabled={!canSubmit}>
      {isSubmitting ? '...' : 'Submit'}
    </button>
  )}
/>

Array Fields

Array fields allow you to manage a list of values within a form, such as a list of hobbies. You can create an array field using the form.Field component with the mode="array" prop. The component accepts a children prop, which is a render prop function that takes an arrayField object as its argument.

When working with array fields, you can use the fields pushValue, removeValue, and swapValues methods to add, remove, and swap values in the array.

Example:

<form.Field
  name="hobbies"
  mode="array"
  children={(hobbiesField) => (
    <div>
      Hobbies
      <div>
        {!hobbiesField.state.value.length
          ? 'No hobbies found.'
          : hobbiesField.state.value.map((_, i) => (
              <div key={i}>
                <hobbiesField.Field
                  index={i}
                  name="name"
                  children={(field) => {
                    return (
                      <div>
                        <label htmlFor={field.name}>Name:</label>
                        <input
                          name={field.name}
                          value={field.state.value}
                          onBlur={field.handleBlur}
                          onChange={(e) => field.handleChange(e.target.value)}
                        />
                        <button
                          type="button"
                          onClick={() => hobbiesField.removeValue(i)}
                        >
                          X
                        </button>
                        <FieldInfo field={field} />
                      </div>
                    )
                  }}
                />
                <hobbiesField.Field
                  index={i}
                  name="description"
                  children={(field) => {
                    return (
                      <div>
                        <label htmlFor={field.name}>Description:</label>
                        <input
                          name={field.name}
                          value={field.state.value}
                          onBlur={field.handleBlur}
                          onChange={(e) => field.handleChange(e.target.value)}
                        />
                        <FieldInfo field={field} />
                      </div>
                    )
                  }}
                />
              </div>
            ))}
      </div>
      <button
        type="button"
        onClick={() =>
          hobbiesField.pushValue({
            name: '',
            description: '',
            yearsOfExperience: 0,
          })
        }
      >
        Add hobby
      </button>
    </div>
  )}
/>

Array-Nested Fields

Rendering fields that are items of a nested array require only a small change to the form.Field component props.

Example:

<hobbiesField.Field
  index={i}
  name="name"
  children={(field) => {
    return (
      <div>
        <label htmlFor={field.name}>Name:</label>
        <input
          name={field.name}
          value={field.state.value}
          onBlur={field.handleBlur}
          onChange={(e) => field.handleChange(e.target.value)}
        />
        <button type="button" onClick={() => hobbiesField.removeValue(i)}>
          X
        </button>
        <FieldInfo field={field} />
      </div>
    )
  }}
/>

These are the basic concepts and terminology used in the @tanstack/react-form library. Understanding these concepts will help you work more effectively with the library and create complex forms with ease.