Create Nested Forms

This page dives into nested forms, a method for structuring complex data and independent validation within forms.

This page explores creating nested forms, a technique for handling complex data structures within forms. Nested forms allow you to group related fields and manage their validation independently.

Use Case

Imagine a user registration form where you want to collect not only basic information (name, email) but also a billing address with separate fields for street, city, state, and zip code. A nested form structure would be ideal for this scenario.

Creating a Nested Form with Validation

Here's a breakdown of creating a nested form.

1. Define the Form Model:

Start by defining a model structure that reflects the nested data you want to collect. In this case, we'll have a user object with a nested billingAddress object.

const user = {
  name: "",
  email: "",
  billingAddress: {
    street: "",
    city: "",
    state: "",
    zip: "",
  },
};

2. Create the Nested Form with Validation

Use the useForm function and define validation rules for both the top-level user object and the nested billingAddress object.

import { useForm, Validators } from "@nattyjs/tidy";

const userForm = useForm(user, {
  validators: {
    name: Validators.required(),
    email: [Validators.required(), Validators.email()],
    billingAddress: {
      street: Validators.required(),
      city: Validators.required(),
      state: Validators.required(),
      zip: [Validators.required(), Validators.pattern({expression:{digit:/^\d{5}$/}})], // US zip code pattern
    },
  },
});

3. Write HTML for Nested Input Controls

Integrate the form object into your HTML template, using dedicated sections for the top-level fields and nested address fields (adapt the syntax to your framework):

<template>
  <div>
    <input type="text" v-model="userForm.name" />
    <input type="text" v-model="userForm.email" />
    
    <h3>Billing Address</h3>
    <input type="text" v-model="userForm.billingAddress.street" />
    <input type="text" v-model="userForm.billingAddress.city" />
    <input type="text" v-model="userForm.billingAddress.state" />
    <input type="text" v-model="userForm.billingAddress.zip" />

    <button :disabled="userForm.valid">Submit</button>
  </div>
<template>

4. Show Validation Errors (similar to Simple Form)

Use the errorMessage property of the corresponding FormControl within the userForm object to display error messages for both top-level and nested fields.

5. Validate Form on Submit (similar to Simple Form):

The submit button should remain disabled until both the top-level and nested forms are valid. Adapt the code from the Simple Form page to your specific framework.

  <button :disabled="!userForm.valid">Submit</button>

Complete Code Walkthrough

<script setup lang="ts">
import { reactive } from 'vue';

import { useForm, Validators } from '@nattyjs/tidy';

const user = {
  name: "",
  email: "",
  billingAddress: {
    street: "",
    city: "",
    state: "",
    zip: "",
  },
};
const userForm = reactive(
  useForm(user, {
  validators: {
    name: Validators.required(),
    email: [Validators.required(), Validators.email()],
    billingAddress: {
      street: Validators.required(),
      city: Validators.required(),
      state: Validators.required(),
      zip: [Validators.required(), Validators.pattern({expression:{digit:/^\d{5}$/}})], // US zip code pattern
    },
  },
})
);
</script>

<template>
 <div>
  <input type="text" v-model="userForm.name" />
  <input type="text" v-model="userForm.email" />
  
  <h3>Billing Address</h3>
  <input type="text" v-model="userForm.billingAddress.street" />
  <input type="text" v-model="userForm.billingAddress.city" />
  <input type="text" v-model="userForm.billingAddress.state" />
  <input type="text" v-model="userForm.billingAddress.zip" />

  <button :disabled="userForm.valid">Submit</button>
</div>
</template>