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