React meta-framework for building enterprise CRUD applications with authentication, data management, and headless UI integration
—
Advanced form management with auto-save, validation, mutation handling, and integration with popular form libraries.
Orchestrates data hooks for create, edit, and clone operations with advanced features like auto-save, validation, and automatic redirects.
/**
* Orchestrates form operations with data management, validation, and auto-save
* @param params - Form configuration options
* @returns Form state, handlers, and mutation controls
*/
function useForm<TQueryFnData = BaseRecord, TError = HttpError, TVariables = {}, TData = TQueryFnData, TResponse = BaseRecord, TResponseError = HttpError>(
params?: UseFormConfig<TQueryFnData, TError, TVariables, TData, TResponse, TResponseError>
): UseFormReturnType<TQueryFnData, TError, TVariables, TData, TResponse, TResponseError>;
interface UseFormConfig<TQueryFnData, TError, TVariables, TData, TResponse, TResponseError> {
/** Form action type - determines behavior */
action?: "create" | "edit" | "clone";
/** Resource name - inferred from route if not provided */
resource?: string;
/** Record ID for edit/clone operations */
id?: BaseKey;
/** Where to redirect after successful submission */
redirect?: RedirectAction;
/** Additional metadata for operations */
meta?: MetaQuery;
/** Mutation mode for optimistic updates */
mutationMode?: MutationMode;
/** Callback on successful mutation */
onMutationSuccess?: (data: TResponse, variables: TVariables, context: any, isAutoSave: boolean) => void;
/** Callback on mutation error */
onMutationError?: (error: TResponseError, variables: TVariables, context: any, isAutoSave: boolean) => void;
/** Auto-save configuration */
autoSave?: {
/** Enable auto-save functionality */
enabled?: boolean;
/** Debounce delay in milliseconds */
debounce?: number;
/** Invalidate queries when component unmounts */
invalidateOnUnmount?: boolean;
};
/** Success notification configuration */
successNotification?: SuccessErrorNotification | false;
/** Error notification configuration */
errorNotification?: SuccessErrorNotification | false;
/** Data provider name to use */
dataProviderName?: string;
/** Query options for data fetching */
queryOptions?: UseQueryOptions<TQueryFnData, TError>;
/** Mutation options for create/update */
createMutationOptions?: UseMutationOptions<TResponse, TResponseError, UseCreateParams<TVariables>>;
/** Mutation options for update operations */
updateMutationOptions?: UseMutationOptions<TResponse, TResponseError, UseUpdateParams<TVariables>>;
/** Timeout for undoable mutations */
undoableTimeout?: number;
/** Cache invalidation configuration */
invalidates?: Array<string>;
}
interface UseFormReturnType<TQueryFnData, TError, TVariables, TData, TResponse, TResponseError> {
/** Form submission handler */
onFinish: (values: TVariables) => Promise<void>;
/** Auto-save handler for form changes */
onFinishAutoSave: (values: TVariables) => Promise<void>;
/** Whether form operations are loading */
formLoading: boolean;
/** Mutation result for create/update operations */
mutation: UseMutationResult<TResponse, TResponseError, UseCreateParams<TVariables> | UseUpdateParams<TVariables>>;
/** Query result for fetching existing data (edit/clone) */
query: UseQueryResult<TQueryFnData, TError>;
/** Auto-save props for form libraries */
autoSaveProps: AutoSaveProps;
/** Current record ID */
id?: BaseKey;
/** Function to set record ID */
setId: React.Dispatch<React.SetStateAction<BaseKey | undefined>>;
/** Redirect configuration */
redirect: RedirectAction;
/** Loading overtime information */
overtime: UseLoadingOvertimeReturnType;
}
interface AutoSaveProps {
/** Current auto-save status */
status: "loading" | "success" | "error" | "idle";
/** Error from auto-save operation */
error?: TResponseError;
/** Data from successful auto-save */
data?: TResponse;
}Usage Example:
import { useForm } from "@refinedev/core";
import { useEffect } from "react";
interface PostFormData {
title: string;
content: string;
status: "draft" | "published";
categoryId: number;
}
function PostForm() {
const {
onFinish,
onFinishAutoSave,
formLoading,
query,
autoSaveProps,
id,
setId
} = useForm<PostFormData>({
action: "edit", // or "create", "clone"
resource: "posts",
redirect: "list",
autoSave: {
enabled: true,
debounce: 2000 // Auto-save after 2 seconds of inactivity
},
onMutationSuccess: (data, variables, context, isAutoSave) => {
if (isAutoSave) {
console.log("Auto-saved successfully");
} else {
console.log("Form submitted successfully");
}
}
});
const [formData, setFormData] = useState<PostFormData>({
title: "",
content: "",
status: "draft",
categoryId: 1
});
// Load existing data for edit mode
useEffect(() => {
if (query.data) {
setFormData(query.data.data);
}
}, [query.data]);
// Auto-save on form changes
useEffect(() => {
const timeoutId = setTimeout(() => {
onFinishAutoSave(formData);
}, 2000);
return () => clearTimeout(timeoutId);
}, [formData, onFinishAutoSave]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await onFinish(formData);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>Title:</label>
<input
value={formData.title}
onChange={(e) => setFormData(prev => ({ ...prev, title: e.target.value }))}
disabled={formLoading}
/>
</div>
<div>
<label>Content:</label>
<textarea
value={formData.content}
onChange={(e) => setFormData(prev => ({ ...prev, content: e.target.value }))}
disabled={formLoading}
/>
</div>
<div>
<label>Status:</label>
<select
value={formData.status}
onChange={(e) => setFormData(prev => ({ ...prev, status: e.target.value as "draft" | "published" }))}
disabled={formLoading}
>
<option value="draft">Draft</option>
<option value="published">Published</option>
</select>
</div>
{/* Auto-save indicator */}
<div className="auto-save-status">
{autoSaveProps.status === "loading" && <span>Saving...</span>}
{autoSaveProps.status === "success" && <span>Saved ✓</span>}
{autoSaveProps.status === "error" && <span>Save failed ✗</span>}
</div>
<button type="submit" disabled={formLoading}>
{formLoading ? "Submitting..." : "Submit"}
</button>
</form>
);
}Integration patterns with React Hook Form for advanced form validation and state management.
/**
* Integration with React Hook Form
* @param params - Configuration for React Hook Form integration
* @returns Combined form state and Refine form handlers
*/
function useFormWithReactHookForm<TFormData, TQueryFnData = BaseRecord, TError = HttpError, TResponse = BaseRecord>(
params?: UseFormConfig<TQueryFnData, TError, TFormData, TQueryFnData, TResponse, TError>
): UseFormWithReactHookFormReturnType<TFormData, TQueryFnData, TError, TResponse>;
interface UseFormWithReactHookFormReturnType<TFormData, TQueryFnData, TError, TResponse> extends UseFormReturnType<TQueryFnData, TError, TFormData, TQueryFnData, TResponse, TError> {
/** Register form fields with React Hook Form */
register: UseFormRegister<TFormData>;
/** Handle form submission with validation */
handleSubmit: UseFormHandleSubmit<TFormData>;
/** Form state and errors */
formState: FormState<TFormData>;
/** Set form values programmatically */
setValue: UseFormSetValue<TFormData>;
/** Get form values */
getValues: UseFormGetValues<TFormData>;
/** Watch form field changes */
watch: UseFormWatch<TFormData>;
}React Hook Form Example:
import { useForm as useRefineForm } from "@refinedev/core";
import { useForm as useReactHookForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
const schema = yup.object({
title: yup.string().required("Title is required"),
content: yup.string().min(10, "Content must be at least 10 characters"),
email: yup.string().email("Invalid email format")
});
function PostFormWithValidation() {
const { onFinish, formLoading, query } = useRefineForm();
const {
register,
handleSubmit,
formState: { errors },
setValue,
watch
} = useReactHookForm({
resolver: yupResolver(schema),
defaultValues: query.data?.data
});
const onSubmit = (data: any) => {
onFinish(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<input {...register("title")} placeholder="Title" />
{errors.title && <span>{errors.title.message}</span>}
</div>
<div>
<textarea {...register("content")} placeholder="Content" />
{errors.content && <span>{errors.content.message}</span>}
</div>
<button type="submit" disabled={formLoading}>
Submit
</button>
</form>
);
}Integration patterns with Formik for form state management and validation.
/**
* Integration with Formik
* @param params - Configuration for Formik integration
* @returns Formik props combined with Refine form handlers
*/
function useFormWithFormik<TFormData>(
params?: UseFormConfig & FormikConfig<TFormData>
): UseFormWithFormikReturnType<TFormData>;
interface UseFormWithFormikReturnType<TFormData> {
/** Formik form props */
formikProps: FormikProps<TFormData>;
/** Refine form handlers */
refineProps: UseFormReturnType;
}Visual indicator for auto-save status with customizable appearance.
/**
* Visual indicator for auto-save status
* @param props - Auto-save indicator configuration
* @returns Auto-save status indicator component
*/
function AutoSaveIndicator(props?: AutoSaveIndicatorProps): JSX.Element;
interface AutoSaveIndicatorProps {
/** Auto-save status */
status: "loading" | "success" | "error" | "idle";
/** Custom messages for each status */
messages?: {
loading?: string;
success?: string;
error?: string;
idle?: string;
};
/** Custom styling */
style?: React.CSSProperties;
/** Custom class names */
className?: string;
}Usage Example:
import { AutoSaveIndicator, useForm } from "@refinedev/core";
function FormWithAutoSave() {
const { autoSaveProps, onFinishAutoSave } = useForm({
autoSave: { enabled: true, debounce: 1000 }
});
return (
<div>
<form>
{/* Form fields */}
</form>
<AutoSaveIndicator
status={autoSaveProps.status}
messages={{
loading: "Saving draft...",
success: "Draft saved",
error: "Failed to save draft"
}}
/>
</div>
);
}Integration with validation libraries for client-side form validation.
/**
* Validation configuration for forms
*/
interface ValidationConfig<TFormData> {
/** Validation schema */
schema?: ValidationSchema<TFormData>;
/** Custom validation functions */
rules?: ValidationRules<TFormData>;
/** Validation mode */
mode?: "onChange" | "onBlur" | "onSubmit";
/** Re-validation mode */
reValidateMode?: "onChange" | "onBlur" | "onSubmit";
}
interface ValidationSchema<TFormData> {
[K in keyof TFormData]?: ValidationRule[];
}
interface ValidationRule {
/** Validation type */
type: "required" | "email" | "min" | "max" | "pattern" | "custom";
/** Validation value */
value?: any;
/** Error message */
message: string;
}
interface ValidationRules<TFormData> {
[K in keyof TFormData]?: (value: TFormData[K], formData: TFormData) => string | undefined;
}Managing different loading states during form operations.
/**
* Form loading state management
*/
interface FormLoadingState {
/** Whether form is submitting */
submitting: boolean;
/** Whether data is being fetched */
fetching: boolean;
/** Whether auto-save is in progress */
autoSaving: boolean;
/** Whether form is validating */
validating: boolean;
}Comprehensive error handling for form operations.
/**
* Form error handling configuration
*/
interface FormErrorHandling<TError> {
/** Field-specific errors */
fieldErrors?: Record<string, string[]>;
/** General form errors */
formErrors?: string[];
/** Server validation errors */
serverErrors?: TError;
/** Error display mode */
errorMode?: "field" | "summary" | "both";
}Support for multi-step form workflows with data persistence.
/**
* Multi-step form management
*/
interface MultiStepFormConfig {
/** Current step index */
currentStep: number;
/** Total number of steps */
totalSteps: number;
/** Step validation configuration */
stepValidation?: Record<number, ValidationConfig>;
/** Data persistence between steps */
persistData?: boolean;
}
interface MultiStepFormActions {
/** Go to next step */
nextStep: () => void;
/** Go to previous step */
previousStep: () => void;
/** Go to specific step */
goToStep: (step: number) => void;
/** Complete the form */
complete: () => void;
}Support for forms with dynamic field generation and conditional logic.
/**
* Dynamic form field configuration
*/
interface DynamicFieldConfig {
/** Field type */
type: "text" | "number" | "select" | "checkbox" | "radio" | "textarea" | "date";
/** Field name/key */
name: string;
/** Display label */
label: string;
/** Default value */
defaultValue?: any;
/** Field options for select/radio */
options?: Array<{ label: string; value: any }>;
/** Validation rules */
validation?: ValidationRule[];
/** Conditional display logic */
condition?: (formData: any) => boolean;
/** Field-specific props */
props?: Record<string, any>;
}type RedirectAction = "list" | "edit" | "show" | "create" | "clone" | false;
interface UseLoadingOvertimeReturnType {
/** Elapsed time since loading started */
elapsedTime?: number;
}
interface FormSubmissionResult<TData> {
/** Whether submission was successful */
success: boolean;
/** Response data */
data?: TData;
/** Error information */
error?: any;
/** Redirect configuration */
redirect?: {
to: string;
type?: "push" | "replace";
};
}
interface FormFieldProps {
/** Field name */
name: string;
/** Field value */
value: any;
/** Change handler */
onChange: (value: any) => void;
/** Blur handler */
onBlur?: () => void;
/** Field error */
error?: string;
/** Whether field is disabled */
disabled?: boolean;
/** Whether field is required */
required?: boolean;
}Install with Tessl CLI
npx tessl i tessl/npm-refinedev--core