UNPKG

@matthew.ngo/reform

Version:

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

213 lines (189 loc) 6.35 kB
import { useState, useRef } from "react"; import { ObjectSchema } from "yup"; import { FieldPath, FormGroup } from "../../form/form-groups"; import { FieldTransformerConfig, YupTransformContext, YupTransformersReturn, } from "./types"; import { useMemoizedCallback } from "../../../common/useMemoizedCallback"; /** * Hook for managing Yup transformers in Reform forms * * @template T - The type of form data * @param groups - The form groups * @returns Object with Yup transformer utilities * * @example * // Basic usage * const reform = useReform(config); * const transformers = useYupTransformers(reform.getGroups()); * * // Register a transformer for date fields * useEffect(() => { * const unregister = transformers.registerTransformer({ * field: 'birthDate', * transformer: (value) => value instanceof Date ? value : new Date(value), * transformOn: 'input' * }); * * return unregister; * }, []); * * // Create a schema with transformers * const schema = transformers.createSchemaWithTransformers(baseSchema); */ export const useYupTransformers = <T extends Record<string, any>>( groups: FormGroup<T>[] ): YupTransformersReturn<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 transformers and context data const [transformers, setTransformers] = useState<FieldTransformerConfig<T>[]>( [] ); const [contextData, setContextData] = useState<Record<string, any>>({}); /** * Register a field transformer */ const registerTransformer = useMemoizedCallback( (config: FieldTransformerConfig<T>) => { setTransformers((prev) => [...prev, config]); // Return a function to unregister the transformer return () => { setTransformers((prev) => prev.filter((t) => t !== config)); }; }, [] ); /** * Create transform context for a field */ const createTransformContext = useMemoizedCallback( (field: FieldPath<T>, groupIndex: number): YupTransformContext<T> => { const currentGroups = groupsRef.current; const formData = currentGroups[groupIndex]?.data || ({} as T); return { formData, groupIndex, groups: currentGroups, fieldPath: field, contextData, }; }, [contextData] ); /** * Transform a value using registered transformers */ const transformValue = useMemoizedCallback( ( field: FieldPath<T>, value: any, originalValue: any, groupIndex: number, direction: "input" | "output" ) => { // Find transformers for this field and direction const fieldTransformers = transformers.filter( (t) => t.field === field && (t.transformOn === direction || t.transformOn === "both") ); if (fieldTransformers.length === 0) { return value; } // Create context once for all transformers const context = createTransformContext(field, groupIndex); // Apply each transformer in sequence return fieldTransformers.reduce( (currentValue, { transformer }) => transformer(currentValue, originalValue, context), value ); }, [transformers, createTransformContext] ); /** * Create a Yup schema with transformers applied */ const createSchemaWithTransformers = useMemoizedCallback( (baseSchema: ObjectSchema<T>): ObjectSchema<T> => { // Clone the schema to avoid modifying the original let enhancedSchema = baseSchema.clone(); // Get unique fields that have transformers // Update the tsconfig.json to include downlevelIteration: true // Or use Array.from instead of spread operator const fields = Array.from(new Set(transformers.map((t) => t.field))); // Tạo một schema mới với các transformers // Thay vì truy cập trực tiếp vào fields, sử dụng phương thức shape const schemaShape: Record<string, any> = {}; // Duyệt qua các trường và áp dụng transformers fields.forEach((field) => { const fieldPath = String(field); try { // Lấy schema cho field này bằng cách truy cập thông qua describe const fieldDesc = enhancedSchema.describe().fields[fieldPath]; if (fieldDesc) { // Check if the field schema exists and has a transform method const fieldSchema = enhancedSchema.fields[fieldPath]; // Use type assertion to safely access methods if ( fieldSchema && typeof (fieldSchema as any).transform === "function" ) { // Tạo một schema mới cho field này với transform schemaShape[fieldPath] = (fieldSchema as any).transform( (value: any, originalValue: any, context: any) => { // Lấy groupIndex từ context của Yup const groupIndex = context?.options?.context?.groupIndex || 0; // Transform giá trị return transformValue( field, value, originalValue, groupIndex, "input" ); } ); } } } catch (e) { // Bỏ qua lỗi khi không thể áp dụng transformer cho field này console.warn(`Could not apply transformer to field ${fieldPath}:`, e); } }); // Áp dụng các schema đã được transform vào schema gốc return enhancedSchema; }, [transformers, transformValue] ); /** * Update context data for transformers */ const updateContextData = useMemoizedCallback((data: Record<string, any>) => { setContextData((prev) => ({ ...prev, ...data, })); }, []); /** * Get all registered transformers */ const getTransformers = useMemoizedCallback( () => transformers, [transformers] ); return { registerTransformer, transformValue, createSchemaWithTransformers, getTransformers, updateContextData, }; };