useForm orchestrates Refine’s data hooks to create, edit, and clone data. It provides a set of features to make it easier to implement real world needs and handle edge cases such as redirects, invalidation, auto-save and more.
Usage
import { useForm } from "@refinedev/core";
const { onFinish, mutation, query, formLoading, redirect } = useForm({
resource: "posts",
action: "create",
});
Props
Resource name for API data interactions.Default: Resource name that it reads from route
action
'create' | 'edit' | 'clone'
Type of the form mode.Default: Action that it reads from route otherwise "create" is used
Record id for fetching. Required for edit and clone actions.Default: Id that it reads from the URL
redirect
'show' | 'edit' | 'list' | 'create' | false
Page to redirect after a successful mutation.Default: "list"
Metadata query for dataProvider.
Metadata to pass for the useOne query.
Metadata to pass for the mutation (useCreate for create and clone actions, useUpdate for edit action).
mutationMode
'pessimistic' | 'optimistic' | 'undoable'
Determines when mutations are executed (pessimistic, optimistic, or undoable).Default: "pessimistic"
onMutationSuccess
(data, variables, context, isAutoSave) => void
Called when a mutation is successful.onMutationSuccess: (data, variables, context, isAutoSave) => {
console.log("Form submitted successfully", data);
}
onMutationError
(error, variables, context, isAutoSave) => void
Called when a mutation encounters an error.onMutationError: (error, variables, context, isAutoSave) => {
console.error("Form submission failed", error);
}
Duration to wait before executing mutations when mutationMode = "undoable".Default: 5000
If there is more than one dataProvider, you should use the dataProviderName that you will use.
invalidates
Array<'all' | 'resourceAll' | 'list' | 'many' | 'detail' | false>
You can use it to manage the invalidations that will occur at the end of the mutation.Default: ["list", "many", "detail"]
react-query’s useQuery options of useOne hook used while in edit mode.
react-query’s useMutation options of useCreate hook used while submitting in create and clone modes.
react-query’s useMutation options of useUpdate hook used while submitting in edit mode.
If you customize the optimisticUpdateMap option, you can use it to manage the invalidations that will occur at the end of the mutation.Default:{
list: true,
many: true,
detail: true,
}
successNotification
OpenNotificationParams | false
Notification configuration for successful mutations.
errorNotification
OpenNotificationParams | false
Notification configuration for failed mutations.
liveMode
'auto' | 'manual' | 'off'
Whether to update data automatically or manually if a related live event is received.
onLiveEvent
(event: LiveEvent) => void
Callback to handle live events.
Additional params to pass to liveProvider’s subscribe method.
Auto-save configuration. Only works in edit mode.Enable auto-save feature.
Debounce time in milliseconds before triggering auto-save.
autoSave.onFinish
(values: TVariables) => TVariables
Transform values before auto-save.
autoSave.invalidateOnUnmount
Whether to invalidate queries when component unmounts.
autoSave.invalidateOnClose
Whether to invalidate queries when modal/drawer closes.
Loading overtime configuration.Interval in milliseconds.
overtimeOptions.onInterval
(elapsedTime: number) => void
Callback to handle interval events.
Return Values
onFinish
(values: TVariables) => Promise<CreateResponse | UpdateResponse | void>
A function to submit the form. Returns a promise that resolves with the mutation result.const handleSubmit = async (values) => {
try {
await onFinish(values);
console.log("Success!");
} catch (error) {
console.error("Error:", error);
}
};
onFinishAutoSave
(values: TVariables) => Promise<UpdateResponse | void>
A debounced function for auto-save. Only available when autoSave.enabled is true.
Result of the react-query’s useMutation. Either from useCreate or useUpdate depending on the action.
query
QueryObserverResult<GetOneResponse<TData>, TError>
Result of the react-query’s useQuery from useOne. Only available in edit and clone modes.
Loading state that combines mutation loading and query fetching states.
setId
React.Dispatch<React.SetStateAction<BaseKey | undefined>>
A function to set the record id.
redirect
(redirect, idFromFunction?, routeParams?) => void
A function to redirect to a specific page.// Redirect to list page
redirect("list");
// Redirect to edit page with specific id
redirect("edit", "123");
// Redirect to show page with route params
redirect("show", "123", { tab: "comments" });
Auto-save status information.autoSaveProps.status
'idle' | 'loading' | 'success' | 'error'
Current auto-save status.
autoSaveProps.data
UpdateResponse | undefined
Data from the last successful auto-save.
Error from the last failed auto-save.
Overtime loading information.Elapsed time in milliseconds.
Example
import { useForm } from "@refinedev/core";
const PostCreate = () => {
const { onFinish, mutation, formLoading } = useForm({
resource: "posts",
action: "create",
redirect: "edit",
});
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const values = Object.fromEntries(formData.entries());
onFinish(values);
};
return (
<form onSubmit={handleSubmit}>
<label>
Title:
<input name="title" type="text" required />
</label>
<label>
Content:
<textarea name="content" required />
</label>
<button type="submit" disabled={formLoading}>
{formLoading ? "Saving..." : "Create Post"}
</button>
</form>
);
};
import { useForm } from "@refinedev/core";
const PostEdit = () => {
const { onFinish, query, formLoading } = useForm({
resource: "posts",
action: "edit",
id: "1",
});
const post = query?.data?.data;
if (!post && formLoading) {
return <div>Loading...</div>;
}
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const values = Object.fromEntries(formData.entries());
onFinish(values);
};
return (
<form onSubmit={handleSubmit}>
<label>
Title:
<input name="title" type="text" defaultValue={post?.title} required />
</label>
<label>
Content:
<textarea name="content" defaultValue={post?.content} required />
</label>
<button type="submit" disabled={formLoading}>
{formLoading ? "Saving..." : "Update Post"}
</button>
</form>
);
};
With Auto-save
import { useForm } from "@refinedev/core";
import { useEffect } from "react";
const PostEdit = () => {
const { onFinishAutoSave, query, autoSaveProps } = useForm({
resource: "posts",
action: "edit",
id: "1",
autoSave: {
enabled: true,
debounce: 2000,
},
});
const post = query?.data?.data;
const handleChange = (e) => {
const formData = new FormData(e.target.form);
const values = Object.fromEntries(formData.entries());
onFinishAutoSave(values);
};
return (
<form>
<div>
{autoSaveProps.status === "loading" && "Saving..."}
{autoSaveProps.status === "success" && "Saved!"}
{autoSaveProps.status === "error" && "Error saving"}
</div>
<label>
Title:
<input
name="title"
type="text"
defaultValue={post?.title}
onChange={handleChange}
/>
</label>
<label>
Content:
<textarea
name="content"
defaultValue={post?.content}
onChange={handleChange}
/>
</label>
</form>
);
};