UNPKG

@matthew.ngo/reform

Version:

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

327 lines (326 loc) 11.6 kB
/// <reference types="react" /> /** * Defines types and interfaces for conditional field rendering and validation. * This module provides the type definitions for dynamically showing, hiding, * and validating fields based on form values. */ import { UseFormRegisterReturn } from 'react-hook-form'; import { FormGroup } from '../../core/form/form-groups'; /** * Base props for conditional rendering components * Contains properties to determine visibility and validation status */ export interface ConditionalRenderProps { /** Whether the field or component should be displayed */ shouldShow: boolean; /** Whether validation should be applied even when hidden */ shouldValidate: boolean; /** * Helper function to conditionally render a component * * @param component - The React component to conditionally render * @returns The component if condition is met, otherwise null * * @example * // Render a field only when user selects "Other" option * const { render } = when(groupIndex, data => data.selection === 'other'); * * return ( * <> * <SelectField name="selection" options={options} /> * {render(<TextField name="otherDetails" label="Please specify" />)} * </> * ); */ render: (component: React.ReactNode) => React.ReactNode | null; } /** * Extended conditional rendering props for group-specific conditions * Includes methods for conditional field registration and validation * * @template T - The type of form data */ export interface GroupConditionalRenderProps<T> extends ConditionalRenderProps { /** * Conditionally registers a field with React Hook Form * If the condition is not met, either returns a dummy register object * or clears the field value based on options * * @template K - The key of the field to register * @param fieldName - The name of the field to register * @param options - Registration options passed to React Hook Form * @returns Either a real or dummy registration object * * @example * // Register a field only when the user is employed * const { registerIf } = when(groupIndex, data => data.employmentStatus === 'employed'); * * return ( * <input * {...registerIf('employerName', { required: 'Employer name is required' })} * placeholder="Employer name" * /> * ); */ registerIf: <K extends keyof T>(fieldName: K, options?: any) => UseFormRegisterReturn | { name: string; onChange: () => void; onBlur: () => void; ref: () => void; }; /** * Conditionally validates a field based on the condition * If the condition is not met and validateWhenHidden is false, * validation is skipped * * @template K - The key of the field to validate * @param fieldName - The name of the field to validate * @param validationFn - Function that validates the field value * @returns Validation error message or undefined if valid/skipped * * @example * // Validate a field only when a checkbox is checked * const { validateIf } = when(groupIndex, data => data.needsVerification); * * const error = validateIf('verificationCode', * value => value?.length !== 6 ? 'Code must be 6 digits' : undefined * ); * * if (error) { * setError(error); * } */ validateIf: <K extends keyof T>(fieldName: K, validationFn: (value: T[K]) => string | undefined) => string | undefined; } /** * Interface for managing conditional fields within a form * Provides methods to show/hide fields based on conditions * * @template T - The type of form data */ export interface ConditionalFieldsManager<T> { /** * Creates a condition based on values within a specific group * * @param groupIndex - The index of the group to evaluate * @param condition - Function that determines if condition is met * @param options - Configuration options for hidden fields * @returns Object with conditional rendering and validation helpers * * @example * // Show additional fields based on selection * const { when } = useReform(formConfig); * * const { render, registerIf } = when( * 0, // first group * data => data.hasChildren === true, * { clearWhenHidden: true } * ); * * return ( * <form> * <Checkbox {...register('hasChildren')} label="Do you have children?" /> * * {render( * <div> * <NumberInput * {...registerIf('childrenCount', { min: 1 })} * label="How many children?" * /> * </div> * )} * </form> * ); * * @example * // Show payment details only when payment method is credit card * const { render, registerIf } = when( * 0, * data => data.paymentMethod === 'creditCard', * { clearWhenHidden: true } * ); * * return ( * <div> * <SelectField * name="paymentMethod" * options={['creditCard', 'bankTransfer', 'paypal']} * /> * * {render( * <div className="credit-card-fields"> * <TextField {...registerIf('cardNumber')} label="Card Number" /> * <TextField {...registerIf('cardName')} label="Name on Card" /> * <div className="flex"> * <TextField {...registerIf('expiryDate')} label="Expiry Date" /> * <TextField {...registerIf('cvv')} label="CVV" /> * </div> * </div> * )} * </div> * ); */ when: (groupIndex: number, condition: (groupData: T) => boolean, options?: { /** Whether to clear field values when hidden (default: true) */ clearWhenHidden?: boolean; /** Whether to validate fields even when hidden (default: false) */ validateWhenHidden?: boolean; }) => GroupConditionalRenderProps<T>; /** * Creates a condition based on values across all groups * * @param condition - Function that determines if condition is met * @param options - Configuration options for hidden fields * @returns Object with conditional rendering helpers * * @example * // Show a summary section only when all groups have data * const { whenAny } = useReform(formConfig); * * const { render } = whenAny( * groups => groups.every(group => * group.data.firstName && group.data.lastName * ) * ); * * return ( * <> * {render( * <SummarySection groups={groups} /> * )} * </> * ); * * @example * // Show summary section only when all required fields are filled * const { render } = whenAny(groups => { * return groups.every(group => * group.data.firstName && * group.data.lastName && * group.data.email * ); * }); * * return ( * <div> * {render( * <div className="summary-panel"> * <h2>Summary</h2> * <SummaryTable data={groups} /> * <SubmitButton /> * </div> * )} * </div> * ); */ whenAny: (condition: (allGroups: FormGroup<T>[]) => boolean, options?: { /** Whether to clear field values when hidden (default: true) */ clearWhenHidden?: boolean; /** Whether to validate fields even when hidden (default: false) */ validateWhenHidden?: boolean; }) => ConditionalRenderProps; /** * Creates a condition that depends on values from multiple groups and the entire form state * * @param condition - Function that evaluates condition based on all form data * @returns Object with conditional rendering helpers * * @example * // Show a component only when total participants across all groups exceeds 10 * const { whenCross } = useReform(formConfig); * * const { render } = whenCross(formData => { * const totalParticipants = formData.groups.reduce( * (sum, group) => sum + (group.data.participants || 0), * 0 * ); * return totalParticipants > 10; * }); * * return ( * <div> * {render( * <div className="large-group-notice"> * <Alert severity="info"> * For groups larger than 10, please contact our group sales department. * </Alert> * <Button>Contact Sales</Button> * </div> * )} * </div> * ); * * @example * // Show discount information when order total exceeds threshold * const { render } = whenCross(formData => { * const orderTotal = formData.groups.reduce((sum, group) => { * return sum + calculateGroupTotal(group.data); * }, 0); * * return orderTotal > 1000; * }); * * return ( * <> * {render( * <DiscountBanner message="You qualify for a 10% bulk discount!" /> * )} * </> * ); */ whenCross: (condition: (formData: { groups: FormGroup<T>[]; }) => boolean) => ConditionalRenderProps; /** * Creates a dependency tracking function that runs effects when specific fields change * * @template K - The keys of fields to track * @param groupIndex - The index of the group containing the fields * @param dependencies - Array of field names to track * @param effect - Function to run when dependencies change * * @example * // Update total price when quantity or price changes * const { createDependencyTracker } = useReform(formConfig); * * createDependencyTracker( * groupIndex, * ['quantity', 'unitPrice'], * ({ quantity, unitPrice }) => { * const total = (quantity || 0) * (unitPrice || 0); * setValue(groupIndex, 'totalPrice', total); * } * ); * * @example * // Calculate total when quantity or price changes * createDependencyTracker( * 0, * ['quantity', 'price'], * ({ quantity, price }) => { * const total = (quantity || 0) * (price || 0); * setValue(0, 'total', total); * } * ); * * @example * // Update shipping options based on country selection * createDependencyTracker( * groupIndex, * ['country'], * ({ country }) => { * if (country) { * const shippingOptions = getShippingOptionsForCountry(country); * setShippingOptions(shippingOptions); * * // Reset shipping method if not available in new country * const currentMethod = getValue(groupIndex, 'shippingMethod'); * if (currentMethod && !shippingOptions.includes(currentMethod)) { * setValue(groupIndex, 'shippingMethod', ''); * } * } * } * ); */ createDependencyTracker: <K extends keyof T>(groupIndex: number, dependencies: K[], effect: (values: Pick<T, K>) => void) => void; }