# 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 `useForm` or [`useAppForm`](./form-composition.md) to create a `form` variable, then reference its `FormGroup` component like you would a `Field`:

```tsx
const form = useForm({
    defaultValues: {
        step1: {
            name: ""
        },
        step2: {
            age: 0
        }
    }
})

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

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

```tsx
const [step, setStep] = useState(0)
const form = useForm({
    defaultValues: {
        step1: {
            name: ""
        },
        step2: {
            age: 0
        }
    }
})

return (
    <>
        {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}
            children={group => (
                // Use `group.handleSubmit()` to submit the sub-form, but not the parent form
                // ...
            )}
        /> : null }
        {step === 1 ? <form.FormGroup
            name="step2"
            children={group => (
                // Then, use `form.handleSubmit()` to submit the entire form
                // ...
            )}
        /> : null }
    </>
)
```

## 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' }}
  children={(group) => {
    group.state.meta.errorMap // {onChange: "Error" | undefined}
    group.state.meta.errors // ("Error")[]
  }}
/>
```

- 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 `useForm`:

```tsx
useForm({
  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
