/** * useFormSync Hook */ import { useState, useEffect, useCallback } from 'react'; import { useRouter } from 'next/router'; import { useAppDispatch, useAppSelector } from '../stores/hooks'; import type { RootState } from '../stores/store'; import type { AsyncThunk } from '@reduxjs/toolkit'; import type { BaseEntity } from '../types/entities'; import type { FetchParams } from '../types/api'; interface UseFormSyncOptions { entitySelector: (state: RootState) => T | T[] | undefined; fetchAction: AsyncThunk; initialValues: TFormValues; transformEntity?: (entity: T) => TFormValues; } interface UseFormSyncReturn { formValues: TFormValues; setFormValues: (values: TFormValues) => void; isLoading: boolean; entityId: string | undefined; resetForm: () => void; } /** * Hook for synchronizing form state with Redux entity data * Replaces the redundant triple-useEffect pattern in edit pages * * @param options - Configuration options * @returns Form state and management functions * * @example * ```typescript * const { formValues, isLoading, entityId } = useFormSync({ * entitySelector: (state) => state.users.data, * fetchAction: fetch, * initialValues: { firstName: '', lastName: '', email: '' }, * }) * ``` */ export function useFormSync< T extends BaseEntity, TFormValues extends Record, >(options: UseFormSyncOptions): UseFormSyncReturn { const { entitySelector, fetchAction, initialValues, transformEntity } = options; const router = useRouter(); const dispatch = useAppDispatch(); const [formValues, setFormValues] = useState(initialValues); const [isLoading, setIsLoading] = useState(false); // Get entity ID from router const entityId = router.query.id as string | undefined; // Get entity data from Redux const entityData = useAppSelector(entitySelector); // Fetch entity data when ID is available useEffect(() => { if (!entityId) return; setIsLoading(true); dispatch(fetchAction({ id: entityId })).finally(() => setIsLoading(false)); }, [entityId, dispatch, fetchAction]); // Sync form values with fetched entity data (single useEffect replaces redundant pair) useEffect(() => { if (!entityData) return; // Handle both single entity and array cases const entity = Array.isArray(entityData) ? undefined : entityData; if (entity && typeof entity === 'object') { if (transformEntity) { setFormValues(transformEntity(entity)); } else { // Default: merge entity data into initial values const newValues = { ...initialValues }; Object.keys(initialValues).forEach((key) => { const entityKey = key as keyof T; if (entityKey in entity) { (newValues as Record)[key] = entity[entityKey]; } }); setFormValues(newValues); } } }, [entityData, initialValues, transformEntity]); // Reset form to initial values const resetForm = useCallback(() => { setFormValues(initialValues); }, [initialValues]); return { formValues, setFormValues, isLoading, entityId, resetForm, }; } export default useFormSync;