@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
text/typescript
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,
};
};