# Reform Conditional Fields

Reform provides a powerful conditional fields system that allows you to show or hide form fields based on the values of other fields. This document covers the conditional fields capabilities of Reform.

## Table of Contents

- [Overview](#overview)
- [Basic Conditional Rendering](#basic-conditional-rendering)
- [Conditional Field Registration](#conditional-field-registration)
- [Cross-Group Conditions](#cross-group-conditions)
- [Dependency Tracking](#dependency-tracking)
- [API Reference](#api-reference)

## Overview

Reform's conditional fields system allows you to:

1. **Conditionally render** form fields or components based on form values
2. **Conditionally register** fields with React Hook Form
3. **Conditionally validate** fields based on other field values
4. **Track dependencies** between fields and run effects when values change

## Basic Conditional Rendering

The most common use case is to show or hide fields based on the value of another field:

```tsx
import { useReform } from '@reform/core';

type UserForm = {
  employmentStatus: 'employed' | 'self-employed' | 'unemployed';
  employerName: string;
  businessName: string;
};

const MyForm = () => {
  const form = useReform<UserForm>({
    defaultData: { 
      employmentStatus: 'unemployed', 
      employerName: '', 
      businessName: '' 
    },
    minGroups: 1,
  });

  // Get conditional rendering props for employed status
  const employedCondition = form.when(
    0, 
    data => data.employmentStatus === 'employed'
  );

  // Get conditional rendering props for self-employed status
  const selfEmployedCondition = form.when(
    0, 
    data => data.employmentStatus === 'self-employed'
  );

  return (
    <form>
      <select {...form.register(0, 'employmentStatus')}>
        <option value="employed">Employed</option>
        <option value="self-employed">Self-employed</option>
        <option value="unemployed">Unemployed</option>
      </select>

      {/* Render employer field only when employed */}
      {employedCondition.render(
        <div>
          <label>Employer Name</label>
          <input {...form.register(0, 'employerName')} />
        </div>
      )}

      {/* Render business field only when self-employed */}
      {selfEmployedCondition.render(
        <div>
          <label>Business Name</label>
          <input {...form.register(0, 'businessName')} />
        </div>
      )}
    </form>
  );
};
```

## Conditional Field Registration

For more control, you can conditionally register fields:

```tsx
import { useReform } from '@reform/core';

type PaymentForm = {
  paymentMethod: 'creditCard' | 'bankTransfer' | 'paypal';
  cardNumber: string;
  cardName: string;
  expiryDate: string;
  cvv: string;
};

const PaymentForm = () => {
  const form = useReform<PaymentForm>({
    defaultData: { 
      paymentMethod: 'bankTransfer',
      cardNumber: '',
      cardName: '',
      expiryDate: '',
      cvv: ''
    },
    minGroups: 1,
  });

  // Get conditional rendering and registration props
  const { render, registerIf } = form.when(
    0,
    data => data.paymentMethod === 'creditCard',
    { clearWhenHidden: true } // Clear field values when hidden
  );

  return (
    <form>
      <select {...form.register(0, 'paymentMethod')}>
        <option value="creditCard">Credit Card</option>
        <option value="bankTransfer">Bank Transfer</option>
        <option value="paypal">PayPal</option>
      </select>

      {render(
        <div className="credit-card-fields">
          <div>
            <label>Card Number</label>
            <input 
              {...registerIf('cardNumber', { 
                required: 'Card number is required' 
              })} 
            />
          </div>
          
          <div>
            <label>Name on Card</label>
            <input 
              {...registerIf('cardName', { 
                required: 'Name on card is required' 
              })} 
            />
          </div>
          
          <div className="flex">
            <div>
              <label>Expiry Date</label>
              <input 
                {...registerIf('expiryDate', { 
                  required: 'Expiry date is required' 
                })} 
              />
            </div>
            
            <div>
              <label>CVV</label>
              <input 
                {...registerIf('cvv', { 
                  required: 'CVV is required',
                  pattern: {
                    value: /^\d{3,4}$/,
                    message: 'CVV must be 3 or 4 digits'
                  }
                })} 
              />
            </div>
          </div>
        </div>
      )}
    </form>
  );
};
```

## Cross-Group Conditions

Reform allows you to create conditions based on values across all groups:

```tsx
import { useReform } from '@reform/core';

type PersonForm = {
  firstName: string;
  lastName: string;
  email: string;
};

const FamilyForm = () => {
  const form = useReform<PersonForm>({
    defaultData: { firstName: '', lastName: '', email: '' },
    minGroups: 1,
  });

  // Add a new person
  const handleAddPerson = () => {
    form.addGroup();
  };

  // Show summary only when all required fields are filled
  const { render } = form.whenAny(groups => {
    return groups.every(group =>
      group.data.firstName &&
      group.data.lastName &&
      group.data.email
    );
  });

  // Show discount message when more than 3 people
  const { render: renderDiscount } = form.whenCross(formData => {
    return formData.groups.length > 3;
  });

  return (
    <div>
      {form.getGroups().map((group, index) => (
        <div key={group.id} className="person-form">
          <h3>Person {index + 1}</h3>
          <input 
            {...form.register(index, 'firstName')} 
            placeholder="First Name" 
          />
          <input 
            {...form.register(index, 'lastName')} 
            placeholder="Last Name" 
          />
          <input 
            {...form.register(index, 'email')} 
            placeholder="Email" 
          />
        </div>
      ))}

      <button type="button" onClick={handleAddPerson}>
        Add Person
      </button>

      {renderDiscount(
        <div className="discount-message">
          Family discount applied for groups larger than 3!
        </div>
      )}

      {render(
        <div className="summary-panel">
          <h2>Summary</h2>
          <table>
            <thead>
              <tr>
                <th>First Name</th>
                <th>Last Name</th>
                <th>Email</th>
              </tr>
            </thead>
            <tbody>
              {form.getGroups().map((group, index) => (
                <tr key={group.id}>
                  <td>{group.data.firstName}</td>
                  <td>{group.data.lastName}</td>
                  <td>{group.data.email}</td>
                </tr>
              ))}
            </tbody>
          </table>
          <button type="submit">Submit</button>
        </div>
      )}
    </div>
  );
};
```

## Dependency Tracking

Reform allows you to track dependencies between fields and run effects when values change:

```tsx
import { useReform } from '@reform/core';
import { useEffect } from 'react';

type OrderForm = {
  quantity: number;
  unitPrice: number;
  totalPrice: number;
};

const OrderForm = () => {
  const form = useReform<OrderForm>({
    defaultData: { quantity: 1, unitPrice: 0, totalPrice: 0 },
    minGroups: 1,
  });

  // Set up dependency tracking
  useEffect(() => {
    form.createDependencyTracker(
      0,
      ['quantity', 'unitPrice'],
      ({ quantity, unitPrice }) => {
        const total = (quantity || 0) * (unitPrice || 0);
        form.setValue(0, 'totalPrice', total);
      }
    );
  }, []);

  return (
    <form>
      <div>
        <label>Quantity</label>
        <input 
          type="number" 
          {...form.register(0, 'quantity')} 
          min="1" 
        />
      </div>
      
      <div>
        <label>Unit Price</label>
        <input 
          type="number" 
          {...form.register(0, 'unitPrice')} 
          step="0.01" 
        />
      </div>
      
      <div>
        <label>Total Price</label>
        <input 
          type="number" 
          {...form.register(0, 'totalPrice')} 
          readOnly 
        />
      </div>
    </form>
  );
};
```

## API Reference

### ConditionalFieldsManager

```typescript
interface ConditionalFieldsManager<T> {
  when(
    groupIndex: number,
    condition: (groupData: T) => boolean,
    options?: {
      clearWhenHidden?: boolean;
      validateWhenHidden?: boolean;
    }
  ): GroupConditionalRenderProps<T>;

  whenAny(
    condition: (allGroups: FormGroup<T>[]) => boolean,
    options?: {
      clearWhenHidden?: boolean;
      validateWhenHidden?: boolean;
    }
  ): ConditionalRenderProps;

  whenCross(
    condition: (formData: { groups: FormGroup<T>[] }) => boolean
  ): ConditionalRenderProps;

  createDependencyTracker<K extends keyof T>(
    groupIndex: number,
    dependencies: K[],
    effect: (values: Pick<T, K>) => void
  ): void;
}
```

### GroupConditionalRenderProps

```typescript
interface GroupConditionalRenderProps<T> extends ConditionalRenderProps {
  registerIf<K extends keyof T>(
    fieldName: K,
    options?: any
  ): UseFormRegisterReturn | {
    name: string;
    onChange: () => void;
    onBlur: () => void;
    ref: () => void;
  };

  validateIf<K extends keyof T>(
    fieldName: K,
    validationFn: (value: T[K]) => string | undefined
  ): string | undefined;
}
```

### ConditionalRenderProps

```typescript
interface ConditionalRenderProps {
  shouldShow: boolean;
  shouldValidate: boolean;
  render: (component: React.ReactNode) => React.ReactNode | null;
}
```