39948-vm/frontend/src/hooks/useFormSync.ts
2026-03-19 07:12:29 +04:00

109 lines
3.2 KiB
TypeScript

/**
* 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<T, TFormValues> {
entitySelector: (state: RootState) => T | T[] | undefined;
fetchAction: AsyncThunk<unknown, FetchParams, { rejectValue: unknown }>;
initialValues: TFormValues;
transformEntity?: (entity: T) => TFormValues;
}
interface UseFormSyncReturn<TFormValues> {
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.users,
* fetchAction: fetch,
* initialValues: { firstName: '', lastName: '', email: '' },
* })
* ```
*/
export function useFormSync<
T extends BaseEntity,
TFormValues extends Record<string, unknown>,
>(options: UseFormSyncOptions<T, TFormValues>): UseFormSyncReturn<TFormValues> {
const { entitySelector, fetchAction, initialValues, transformEntity } =
options;
const router = useRouter();
const dispatch = useAppDispatch();
const [formValues, setFormValues] = useState<TFormValues>(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<string, unknown>)[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;