UNPKG

@matthew.ngo/reform

Version:

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

166 lines (144 loc) 4.69 kB
import { useState, useRef } from 'react'; import { ObjectSchema, ValidationError } from 'yup'; import { FieldPath, FormGroup } from '../../form/form-groups'; import { EnhancedValidationError, YupContextReturn, YupValidationContext, } from './types'; import { useMemoizedCallback } from '../../../common/useMemoizedCallback'; /** * Hook for managing Yup validation context in Reform forms * * @template T - The type of form data * @param groups - The form groups * @returns Object with Yup context utilities * * @example * // Basic usage * const reform = useReform(config); * const yupContext = useYupContext(reform.getGroups()); * * // Create a schema with context * const schema = yupContext.createSchemaWithContext(baseSchema); * * // Add additional context data * useEffect(() => { * yupContext.updateContextData({ * currentUser: user, * permissions: userPermissions * }); * }, [user, userPermissions]); */ export const useYupContext = <T extends Record<string, any>>( groups: FormGroup<T>[] ): YupContextReturn<T> => { // Store groups reference to avoid unnecessary re-renders const groupsRef = useRef(groups); // Update groups reference when it changes if (groupsRef.current !== groups) { groupsRef.current = groups; } // State for context data const [contextData, setContextData] = useState<Record<string, any>>({}); /** * Create a validation context for Yup */ const createValidationContext = useMemoizedCallback( (groupIndex: number, fieldPath?: FieldPath<T>): YupValidationContext<T> => { const currentGroups = groupsRef.current; const formData = currentGroups[groupIndex]?.data || ({} as T); return { groups: currentGroups, groupIndex, formData, contextData, fieldPath, }; }, [contextData] ); /** * Create a Yup schema with context */ const createSchemaWithContext = useMemoizedCallback( (baseSchema: ObjectSchema<T>): ObjectSchema<T> => { // Clone the schema to avoid modifying the original const enhancedSchema = baseSchema.clone(); // Không thể thêm phương thức withContext trực tiếp vào schema // Thay vào đó, tạo một hàm wrapper để xử lý context const validateWithContext = async ( data: T, context: YupValidationContext<T> ) => { try { // Truyền context vào options của validate const result = await enhancedSchema.validate(data, { context, abortEarly: false, }); return { value: result, error: null }; } catch (error) { return { value: null, error: error as ValidationError }; } }; // Gán phương thức validate với context vào schema thông qua thuộc tính tùy chỉnh (enhancedSchema as any).validateWithContext = validateWithContext; return enhancedSchema; }, [] ); /** * Update context data */ const updateContextData = useMemoizedCallback((data: Record<string, any>) => { setContextData(prev => ({ ...prev, ...data, })); }, []); /** * Get current context data */ const getContextData = useMemoizedCallback(() => contextData, [contextData]); /** * Format Yup validation errors with enhanced context */ const formatValidationError = useMemoizedCallback( (error: ValidationError, groupIndex: number): EnhancedValidationError => { const enhancedError = error as EnhancedValidationError; // Add additional context to the error enhancedError.groupIndex = groupIndex; // Extract field path from the error path if available if (error.path) { enhancedError.fieldPath = error.path; // Try to get the field value that caused the error const currentGroups = groupsRef.current; const formData = currentGroups[groupIndex]?.data || ({} as T); try { // Use path to access nested properties const pathParts = error.path.split('.'); let value = formData as any; for (const part of pathParts) { value = value[part]; if (value === undefined) break; } enhancedError.fieldValue = value; } catch (e) { // Ignore errors when trying to access field value } } // Add context data enhancedError.context = { ...contextData }; return enhancedError; }, [contextData] ); return { createValidationContext, createSchemaWithContext, updateContextData, getContextData, formatValidationError, }; };