UNPKG

@matthew.ngo/reform

Version:

A flexible and powerful React form management library with advanced validation, state observation, and multi-group support

404 lines (345 loc) 9.82 kB
# 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; } ```