- Extract API error handling utility (utils/error-handling.ts) - Centralize error message extraction logic - Add type guards for API errors - Replace duplicated error handling across components - Create reusable Toast component (components/Toast.tsx) - Extract toast notification logic from profile page - Support auto-dismiss functionality - Consistent styling with shared styles - Extract form validation debouncing hook (hooks/useDebouncedValidation.ts) - Reusable debounced validation logic - Clean timeout management - Used in profile page for form validation - Consolidate duplicate styles (styles/auth-form.ts) - Use shared style tokens instead of duplicating values - Reduce code duplication between auth-form and shared styles - Extract loading state component (components/LoadingState.tsx) - Standardize loading UI across pages - Replace duplicated loading JSX patterns - Used in profile, exchange, and trades pages - Fix useRequireAuth dependency array - Remove unnecessary hasPermission from dependencies - Add eslint-disable comment with explanation - Improve hook stability and performance All frontend tests pass. Linting passes.
54 lines
1.7 KiB
TypeScript
54 lines
1.7 KiB
TypeScript
import { useEffect, useRef, useState } from "react";
|
|
|
|
/**
|
|
* Hook for debounced form validation.
|
|
* Validates form data after the user stops typing for a specified delay.
|
|
*
|
|
* @param formData - The form data to validate
|
|
* @param validator - Function that validates the form data and returns field errors
|
|
* @param delay - Debounce delay in milliseconds (default: 500)
|
|
* @returns Object containing current errors and a function to manually trigger validation
|
|
*/
|
|
export function useDebouncedValidation<T>(
|
|
formData: T,
|
|
validator: (data: T) => Record<string, string>,
|
|
delay: number = 500
|
|
): {
|
|
errors: Record<string, string>;
|
|
setErrors: React.Dispatch<React.SetStateAction<Record<string, string>>>;
|
|
validate: (data?: T) => void;
|
|
} {
|
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
const validationTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
const formDataRef = useRef<T>(formData);
|
|
|
|
// Keep formDataRef in sync with formData
|
|
useEffect(() => {
|
|
formDataRef.current = formData;
|
|
}, [formData]);
|
|
|
|
// Cleanup timeout on unmount
|
|
useEffect(() => {
|
|
return () => {
|
|
if (validationTimeoutRef.current) {
|
|
clearTimeout(validationTimeoutRef.current);
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
const validate = (data?: T) => {
|
|
// Clear any pending validation timeout
|
|
if (validationTimeoutRef.current) {
|
|
clearTimeout(validationTimeoutRef.current);
|
|
}
|
|
|
|
// Debounce validation - wait for user to stop typing
|
|
validationTimeoutRef.current = setTimeout(() => {
|
|
const dataToValidate = data ?? formDataRef.current;
|
|
const newErrors = validator(dataToValidate);
|
|
setErrors(newErrors);
|
|
}, delay);
|
|
};
|
|
|
|
return { errors, setErrors, validate };
|
|
}
|