Skip to Content
ContentGuidesDynamic Forms

Dynamic Forms

Learn how to create forms that adapt based on user input and data.

💡 Tip

Dynamic forms make your UI more intuitive by showing only relevant fields based on user selections. This reduces cognitive load and improves user experience.

Learn how to create forms that adapt based on user input and data.

Conditional Fields

Show or hide fields based on form data using the ConditionalField component.

ℹ️ Info

Conditional fields are completely removed from the DOM when hidden, which improves performance and prevents validation issues.

Basic Conditional Field

import { ConditionalField, FormFieldHelpers } from "@rachelallyson/hero-hook-form"; const fields = [ FormFieldHelpers.checkbox("hasPhone", "I have a phone number"), ConditionalField({ name: "phone", label: "Phone Number", type: "input", condition: (values) => values.hasPhone === true, }), ];
💡 Tip

Always use explicit boolean comparisons (=== true) in conditions for clarity and to avoid truthy/falsy issues.

Complex Conditions

const fields = [ FormFieldHelpers.select("userType", "User Type", [ { label: "Individual", value: "individual" }, { label: "Business", value: "business" }, ]), ConditionalField({ name: "businessName", label: "Business Name", type: "input", condition: (values) => values.userType === "business", }), ConditionalField({ name: "taxId", label: "Tax ID", type: "input", condition: (values) => values.userType === "business" && values.businessName, }), ];

Multiple Conditions

const fields = [ FormFieldHelpers.checkbox("isVip", "VIP Customer"), FormFieldHelpers.checkbox("wantsNewsletter", "Subscribe to newsletter"), ConditionalField({ name: "vipCode", label: "VIP Code", type: "input", condition: (values) => values.isVip && values.wantsNewsletter, }), ];

Field Arrays

Create dynamic repeating field groups with the FieldArrayField component.

ℹ️ Info

Field arrays allow users to add and remove multiple instances of a field group, perfect for addresses, items, contacts, etc.

Basic Field Array

import { FieldArrayField, FormFieldHelpers } from "@rachelallyson/hero-hook-form"; const fields = [ FormFieldHelpers.input("name", "Name"), FieldArrayField({ name: "addresses", fields: [ FormFieldHelpers.input("street", "Street Address"), FormFieldHelpers.input("city", "City"), FormFieldHelpers.input("zipCode", "ZIP Code"), ], addButtonText: "Add Address", removeButtonText: "Remove Address", }), ];
💡 Tip

Set min and max props to control how many items users can add or remove.

Field Array with Validation

import { z } from "zod"; const schema = z.object({ name: z.string().min(2), addresses: z.array(z.object({ street: z.string().min(1, "Street is required"), city: z.string().min(1, "City is required"), zipCode: z.string().min(5, "ZIP code must be at least 5 characters"), })).min(1, "At least one address is required"), });

Field Array with Min/Max

FieldArrayField({ name: "skills", fields: [ FormFieldHelpers.input("name", "Skill Name"), FormFieldHelpers.select("level", "Level", [ { label: "Beginner", value: "beginner" }, { label: "Intermediate", value: "intermediate" }, { label: "Advanced", value: "advanced" }, ]), ], min: 1, max: 5, addButtonText: "Add Skill", removeButtonText: "Remove Skill", })

Dynamic Sections

Group related conditional fields together with the DynamicSectionField component.

ℹ️ Info

Use dynamic sections when you need to show/hide multiple related fields together. They provide better visual organization than individual conditional fields.

Basic Dynamic Section

import { DynamicSectionField, FormFieldHelpers } from "@rachelallyson/hero-hook-form"; const fields = [ FormFieldHelpers.checkbox("hasEmergencyContact", "Has Emergency Contact"), DynamicSectionField({ name: "emergencyContact", title: "Emergency Contact Information", description: "Please provide emergency contact details", condition: (values) => values.hasEmergencyContact === true, fields: [ FormFieldHelpers.input("name", "Contact Name"), FormFieldHelpers.input("relationship", "Relationship"), FormFieldHelpers.input("phone", "Phone Number", "tel"), FormFieldHelpers.input("email", "Email", "email"), ], }), ];
💡 Tip

Dynamic sections can be nested for complex conditional logic. See the nested example below.

Nested Dynamic Sections

const fields = [ FormFieldHelpers.select("accountType", "Account Type", [ { label: "Personal", value: "personal" }, { label: "Business", value: "business" }, ]), DynamicSectionField({ name: "businessInfo", title: "Business Information", condition: (values) => values.accountType === "business", fields: [ FormFieldHelpers.input("businessName", "Business Name"), FormFieldHelpers.input("taxId", "Tax ID"), DynamicSectionField({ name: "billingAddress", title: "Billing Address", condition: (values) => values.businessName && values.taxId, fields: [ FormFieldHelpers.input("street", "Street Address"), FormFieldHelpers.input("city", "City"), FormFieldHelpers.input("state", "State"), FormFieldHelpers.input("zipCode", "ZIP Code"), ], }), ], }), ];

Advanced Patterns

Multi-Step Forms

const steps = [ { id: "personal", title: "Personal Information", fields: [ FormFieldHelpers.input("firstName", "First Name"), FormFieldHelpers.input("lastName", "Last Name"), FormFieldHelpers.input("email", "Email", "email"), ], }, { id: "address", title: "Address Information", fields: [ FormFieldHelpers.input("street", "Street Address"), FormFieldHelpers.input("city", "City"), FormFieldHelpers.input("zipCode", "ZIP Code"), ], }, { id: "preferences", title: "Preferences", fields: [ FormFieldHelpers.checkbox("newsletter", "Subscribe to newsletter"), FormFieldHelpers.checkbox("notifications", "Enable notifications"), ], }, ];

Dependent Dropdowns

const fields = [ FormFieldHelpers.select("country", "Country", [ { label: "Select Country", value: "" }, { label: "United States", value: "us" }, { label: "Canada", value: "ca" }, ]), ConditionalField({ name: "state", label: "State/Province", type: "select", condition: (values) => values.country === "us", selectProps: { options: [ { label: "Select State", value: "" }, { label: "California", value: "ca" }, { label: "New York", value: "ny" }, ], }, }), ConditionalField({ name: "province", label: "Province", type: "select", condition: (values) => values.country === "ca", selectProps: { options: [ { label: "Select Province", value: "" }, { label: "Ontario", value: "on" }, { label: "Quebec", value: "qc" }, ], }, }), ];

Dynamic Validation

import { z } from "zod"; const schema = z.object({ hasPhone: z.boolean(), phone: z.string().optional(), hasAddress: z.boolean(), address: z.object({ street: z.string(), city: z.string(), zipCode: z.string(), }).optional(), }).refine(data => { if (data.hasPhone && !data.phone) { return false; } if (data.hasAddress && (!data.address?.street || !data.address?.city)) { return false; } return true; }, { message: "Required fields are missing", });

Performance Considerations

Memoized Conditional Fields

import { memo } from "react"; const MemoizedConditionalField = memo(ConditionalField); // Use in your form const fields = [ MemoizedConditionalField({ name: "phone", label: "Phone", type: "input", condition: (values) => values.hasPhone, }), ];

Debounced Field Arrays

import { useDebouncedValidation } from "@rachelallyson/hero-hook-form"; function DynamicFieldArray({ fields, ...props }) { const { debouncedValue } = useDebouncedValidation(fields, { delay: 300 }); return ( <FieldArrayField {...props} fields={debouncedValue} /> ); }

Testing Dynamic Forms

Testing Conditional Fields

import { createFormTestUtils } from "@rachelallyson/hero-hook-form"; const testUtils = createFormTestUtils(form); // Test conditional field visibility testUtils.setFieldValue("hasPhone", true); cy.get('[data-testid="phone-field"]').should("be.visible"); testUtils.setFieldValue("hasPhone", false); cy.get('[data-testid="phone-field"]').should("not.exist");

Testing Field Arrays

// Test adding items cy.get('[data-testid="add-address"]').click(); cy.get('[data-testid="addresses[0].street"]').type("123 Main St"); // Test removing items cy.get('[data-testid="remove-address-0"]').click(); cy.get('[data-testid="addresses[0].street"]').should("not.exist");

Best Practices

1. Keep Conditions Simple

💡 Tip

Simple conditions are easier to understand, test, and maintain.

// ✅ Good: Simple condition condition: (values) => values.hasPhone === true // ❌ Avoid: Complex conditions condition: (values) => values.hasPhone && values.userType === "premium" && values.isActive && values.subscriptionStatus === "active"

2. Use Meaningful Field Names

// ✅ Good: Clear naming name: "emergencyContactPhone" // ❌ Avoid: Unclear naming name: "phone2"

3. Provide Clear Labels

// ✅ Good: Descriptive labels label: "Emergency Contact Phone Number" // ❌ Avoid: Generic labels label: "Phone"

4. Handle Edge Cases

⚠️ Warning

Always provide fallback values and handle undefined/null cases in conditions.

// ✅ Good: Safe condition with fallbacks const condition = (values) => { return values?.hasPhone === true && values?.phone?.length > 0; }; // ❌ Bad: No null checking const condition = (values) => { return values.hasPhone && values.phone.length > 0; // May throw error };

5. Test All Conditions

ℹ️ Info

Test both visible and hidden states to ensure conditional fields work correctly.

// Test both true and false states it("shows phone field when hasPhone is true", () => { // Test visible state }); it("hides phone field when hasPhone is false", () => { // Test hidden state });

Next Steps

Last updated on