react-antd-admin-panel
Version:
Modern TypeScript-first React admin panel builder with Ant Design 6
155 lines • 4.95 kB
JavaScript
import { useState, useCallback, useRef } from 'react';
import { useForm as useReactHookForm } from 'react-hook-form';
import { Post } from '../http/Post';
/**
* useForm - React hook for form state management
*
* Wraps react-hook-form with a simplified API and optional Post integration.
*
* @template T - Form values type
* @param options - Hook options
* @returns Hook result with form state and control functions
*
* @example
* ```tsx
* // Basic usage
* const { values, setValue, submit, submitting } = useForm({
* initialValues: { name: '', email: '' },
* onSubmit: async (values) => {
* await saveUser(values);
* },
* });
*
* // With Post integration
* const { values, submit, submitting } = useForm({
* initialValues: { name: '', email: '' },
* post: {
* url: '/api/users',
* onSuccess: (user) => navigate(`/users/${user.id}`),
* },
* });
*
* // With validation
* const { values, errors, submit } = useForm({
* initialValues: { email: '' },
* validate: (values) => {
* const errors: Record<string, string> = {};
* if (!values.email.includes('@')) {
* errors.email = 'Invalid email';
* }
* return errors;
* },
* });
*
* // In JSX
* <input
* value={values.name}
* onChange={(e) => setValue('name', e.target.value)}
* />
* <span>{errors.name?.message}</span>
* <button onClick={submit} disabled={submitting}>Save</button>
* ```
*/
export function useForm(options) {
const { initialValues, mode = 'onSubmit', } = options;
const [submitting, setSubmitting] = useState(false);
const optionsRef = useRef(options);
optionsRef.current = options;
// Use react-hook-form under the hood
const form = useReactHookForm({
defaultValues: initialValues,
mode,
});
const { register, reset: rhfReset, formState: { errors, isDirty, isValid }, setValue: rhfSetValue, getValues, getFieldState, trigger, clearErrors, setError: rhfSetError, watch, } = form;
/**
* Set a single field value
*/
const setValue = useCallback((name, value) => {
rhfSetValue(name, value, { shouldValidate: true, shouldDirty: true });
}, [rhfSetValue]);
/**
* Set multiple field values
*/
const setValues = useCallback((values) => {
Object.entries(values).forEach(([key, value]) => {
rhfSetValue(key, value, { shouldValidate: true, shouldDirty: true });
});
}, [rhfSetValue]);
/**
* Submit the form
*/
const submit = useCallback(async () => {
const currentOptions = optionsRef.current;
// Trigger validation first
const isFormValid = await trigger();
if (!isFormValid) {
return;
}
const values = getValues();
// Run custom validation if provided
if (currentOptions.validate) {
const validationErrors = await currentOptions.validate(values);
if (Object.keys(validationErrors).length > 0) {
// Set errors on form
Object.entries(validationErrors).forEach(([field, message]) => {
rhfSetError(field, { type: 'validate', message });
});
return;
}
}
setSubmitting(true);
try {
// Use Post if configured
if (currentOptions.post) {
const postInstance = new Post()
.target(currentOptions.post.url)
.method(currentOptions.post.method || 'POST')
.body(values);
if (currentOptions.post.headers) {
postInstance.headers(currentOptions.post.headers);
}
const response = await postInstance.execute();
currentOptions.post.onSuccess?.(response);
}
else if (currentOptions.onSubmit) {
// Use onSubmit callback
await currentOptions.onSubmit(values);
}
}
catch (error) {
if (currentOptions.post?.onError) {
currentOptions.post.onError(error instanceof Error ? error : new Error(String(error)));
}
throw error;
}
finally {
setSubmitting(false);
}
}, [trigger, getValues, rhfSetError]);
/**
* Reset form to initial or provided values
*/
const reset = useCallback((values) => {
rhfReset(values ?? initialValues);
}, [rhfReset, initialValues]);
// Get current values (reactive)
const values = watch();
return {
values: values,
errors,
setValue,
setValues,
submit,
submitting,
reset,
isDirty,
isValid,
register,
getFieldState,
trigger,
clearErrors,
setError: rhfSetError,
watch,
};
}
//# sourceMappingURL=useForm.js.map