arbret/frontend/app/hooks/useDebouncedValidation.ts
counterweight 3beb23a765
refactor(frontend): improve code quality and maintainability
- 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.
2025-12-25 19:04:45 +01:00

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 };
}