# Form Groups

When building a multi-stage form that has many stages, like so:

![Form stepper](https://raw.githubusercontent.com/TanStack/form/main/docs/assets/stepper.png)

It's common for each step to have its own form. However, this complicates the form submission and validation process by requiring you to add complex logic.

Luckily, TanStack Form provides a way to build out sub-forms that make this kind of development trivial to implement: `<form.FormGroup>`.

## Usage

To use a form group in TanStack Form, you'll use `createForm` or [`useAppForm`](./form-composition.md) to create a `form` variable, then reference its `FormGroup` component like you would a `Field`:

```tsx
const form = createForm(() => ({
  defaultValues: {
    step1: {
      name: '',
    },
    step2: {
      age: 0,
    },
  },
}))

return (
  <form.FormGroup name="step1">
    {(group) => (
      // `group()` here has all of the form-like methods you'd expect like `deleteField` or `insertFieldValue`
      // ...
    )}
  </form.FormGroup>
)
```

This becomes much more useful when paired with external state to conditionally render a `FormGroup`:

```tsx
const [step, setStep] = createSignal(0)
const form = createForm(() => ({
  defaultValues: {
    step1: {
      name: '',
    },
    step2: {
      age: 0,
    },
  },
}))

return (
  <>
    <Show when={step() === 0}>
      <form.FormGroup
        name="step1"
        onGroupSubmit={() => {
          // We can move the step forward when validation passes
          setStep(step() + 1)
        }}
        onGroupSubmitInvalid={() => {
          // Or handle invalid submissions, just like a top-level form
        }}
        onSubmitMeta={{} as SomeType}
      >
        {(group) => (
          // Use `group().handleSubmit()` to submit the sub-form, but not the parent form
          // ...
        )}
      </form.FormGroup>
    </Show>
    <Show when={step() === 1}>
      <form.FormGroup name="step2">
        {(group) => (
          // Then, use `form.handleSubmit()` to submit the entire form
          // ...
        )}
      </form.FormGroup>
    </Show>
  </>
)
```

## Form Group Validation

Form groups have a distinct validation proceedure that we think makes sense for sub-forms:

- Form groups can have their own validation:

```tsx
<form.FormGroup name="step1" validators={{ onChange: () => 'Error' }}>
  {(group) => {
    group().state.meta.errorMap // {onChange: "Error" | undefined}
    group().state.meta.errors // ("Error")[]
  }}
</form.FormGroup>
```

- Can set errors on sub-fields:

```tsx
<form.FormGroup
  name="step1"
  validators={{
    onChange: ({ value, groupApi }) => ({
      group: value.name === 'error' ? 'Group error' : undefined,
      fields: {
        // Must use the name of the field relative to the FormGroup as the error key,
        // to stay consistent with how standard schema works with form groups
        name: value.name === 'error' ? 'Field error' : undefined,
      },
    }),
  }}
/>
```

- And can even accept standard schemas:

```tsx
<form.FormGroup
  name="step1"
  validators={{
    onChange: z.object({
      name: z.string().min(2),
    }),
  }}
/>
```

> The reason we don't use the full path names for fields is so that you can compose your schemas like so:
>
> ```
> const step1Schema = z.object({
>     name: z.string().min(2)
> })
>
> const schema = z.object({
>     step1: step1Schema,
>     step2: step2Schema
> })
> ```
>
> And pass the `step1Schema` to a form group and `schema` to the parent form. That way, partially validated data will still flag errors if the group is bypassed.

### Dynamic Group Validation

If you want to use [dynamic validation (`onDynamic`)](./dynamic-validation.md) with a form group, do not rely on the `onDynamic` validator passed to `createForm`:

```tsx
createForm(() => ({
  validationLogic: revalidateLogic(),
  validators: {
    // This validator will not run `onChange` when a sub-form is submitted;
    // it will only run `onChange` when the form itself is submitted.
    onDynamic: schema,
  },
}))
```

Instead, pass your sub-schema for the group to the `onDynamic` validation of the `FormGroup` itself:

```tsx
<form.FormGroup validators={{ onDynamic: step1Schema }} />
```

It will treat `group().submissionAttempts` as the way to change what validator is ran before/after submit.

## Form Group State

Just like you're able to access `group().state.meta.errors`, you're also able to access the group's value using `group().state.value`. Likewise, here are some valuable properties you can access in the `group().state.meta`:

- `group().state.meta.isFieldsValid`: `true` when the field-level validators have no errors
- `group().state.meta.isGroupValid`: `true` when the group-level validators have no errors
- `group().state.meta.isValid`: `true` when both the field-level and group-level validators have no errors
- `group().state.meta.isSubmitting`: `true` when the group is in the process of being submitted
