Submitting Data
While useFetch is for fetching and displaying data, useSubmit is its counterpart, designed for actions that change
data on the server, such as creating, updating, or deleting resources. You use it when you need to trigger a request
manually in response to a user event, like clicking a button.
A key feature of useSubmit is that its submit function returns a promise, allowing you to easily perform actions
after the submission completes, like resetting a form or showing a notification.
- How to use the
useSubmithook to send data to the server. - How to handle form submissions for creating and updating data.
- How to trigger actions like deleting an item.
- How the
submitfunction works as a promise for easy chaining of actions. - How to manage loading states (
submitting) to provide user feedback.
Managing a To-Do List
Let's build a complete example of a to-do list application. We'll use useSubmit for all the mutation logic: adding a
new to-do, toggling its completion status, and deleting it.
function TodoApp() {
const { data: todos, loading, refetch } = useFetch(getTodos);
const [newTodo, setNewTodo] = React.useState("");
// 1. Setup submit hooks for each action
const { submit: addTodo, submitting: isAdding } = useSubmit(createTodo);
const { submit: updateTodo } = useSubmit(updateTodoRequest);
const { submit: deleteTodo } = useSubmit(deleteTodoRequest);
const handleAddTodo = async (e) => {
e.preventDefault();
if (!newTodo.trim()) return;
// The submit function returns a promise
await addTodo({ payload: { title: newTodo, completed: false } });
// After success, clear the input and refetch the list
setNewTodo("");
refetch();
};
const handleToggleTodo = async (todo) => {
await updateTodo({
params: { todoId: todo.id },
payload: { completed: !todo.completed },
});
refetch();
};
const handleDeleteTodo = async (todoId) => {
await deleteTodo({ params: { todoId } });
refetch();
};
if (loading) return <p className="p-4">Loading to-do list...</p>;
return (
<div className="border rounded-md">
<div className="p-4 border-b">
<h2 className="text-xl font-bold">My To-Do List</h2>
<form onSubmit={handleAddTodo} className="flex items-center mt-4">
<input
type="text"
value={newTodo}
onChange={(e) => setNewTodo(e.target.value)}
placeholder="What needs to be done?"
className="flex-grow p-2 border rounded-l-md"
/>
<button
type="submit"
disabled={isAdding}
className="p-2 bg-blue-500 text-white rounded-r-md hover:bg-blue-600 disabled:bg-gray-400"
>
{isAdding ? "Adding..." : "Add Todo"}
</button>
</form>
</div>
<ul className="divide-y divide-gray-200">
{todos?.map((todo) => (
<li key={todo.id} className="p-4 flex items-center justify-between">
<span
onClick={() => handleToggleTodo(todo)}
className={`cursor-pointer ${todo.completed ? "line-through text-gray-500" : ""}`}
>
{todo.title}
</span>
<button onClick={() => handleDeleteTodo(todo.id)} className="text-red-500 hover:text-red-700">
Delete
</button>
</li>
))}
</ul>
</div>
);
}
How It Works
- Setup: We set up separate
useSubmithooks for each mutation:createTodo,updateTodoRequest, anddeleteTodoRequest. This keeps the logic for each action clean and separate. - Creating: The
handleAddTodofunction callsaddTodowith the new to-do's data. Becausesubmitis a promise, we canawaitit and then run code after it completes, such as clearing the input field and callingrefetch()to refresh the to-do list. - Updating:
handleToggleTodocallsupdateTodowith thetodoIdinparamsand the newcompletedstatus inpayload. We then refetch to show the change. - Deleting:
handleDeleteTodocallsdeleteTodowith the appropriatetodoIdand refetches. - Loading State: The
submittingboolean (which we aliased toisAddingfor the create action) is used to disable the "Add" button while the request is in flight, preventing duplicate submissions.
This example demonstrates how useSubmit can be the backbone for all the interactive, data-changing parts of your
application.
Lifecycle Callbacks
The useSubmit hook returns registrar functions for monitoring the submission lifecycle. These are called outside of the
JSX, similar to React's effect hooks:
function ContactForm() {
const { submit, submitting, onSubmitSuccess, onSubmitError, onSubmitFinished } = useSubmit(sendMessage);
onSubmitSuccess(({ response }) => {
toast.success("Message sent successfully!");
});
onSubmitError(({ response }) => {
toast.error(`Failed: ${response.error?.message}`);
});
onSubmitFinished(({ response }) => {
console.log("Submission complete", response.success);
});
return (
<button onClick={() => submit({ payload: { text: "Hello" } })} disabled={submitting}>
{submitting ? "Sending..." : "Send Message"}
</button>
);
}
Available lifecycle registrars: onSubmitSuccess, onSubmitError, onSubmitFinished, onSubmitRequestStart,
onSubmitResponseStart, onSubmitAbort, onSubmitOfflineError, onSubmitDownloadProgress,
onSubmitUploadProgress.
You are now a pro at submitting data with useSubmit!
- You can perform create, update, and delete operations with ease.
- You know how to use the
submitpromise to chain actions after a request. - You can manage loading states to create a responsive and intuitive UI.
- You can refetch data from
useFetchto keep your UI in sync with server changes.
