MENU
useSubmit
<Form> submits when a user clicks a submit button or presses enter, but sometimes you want to trigger a route's action from code instead – after a debounced keystroke in a search box, or after a window.confirm() dialog before deleting something. useSubmit() returns an imperative submit(target, options) function that does exactly what a real form submission would do, without needing an actual submit event.
target can be a form element, a FormData object, or a plain object of values, and options accepts the same things <Form> does, like method and replace. Like the rest of the hooks in this section, useSubmit() only works underneath a data router (see the Routers section).
import React, { useRef } from "react";
import { createBrowserRouter, Form, useLoaderData, useSubmit } from "react-router";
import { RouterProvider } from "react-router/dom";
const contacts = ["Alice", "Alicia", "Bob", "Bobby", "Charlie"];
const router = createBrowserRouter([
{
path: "/",
element: <SearchPage />,
loader: async ({ request }) => {
let query = new URL(request.url).searchParams.get("q") || "";
let results = query
? contacts.filter((name) => name.toLowerCase().includes(query.toLowerCase()))
: contacts;
return { query, results };
},
},
]);
export default function UseSubmitExample() {
return <RouterProvider router={router} />;
}
function SearchPage() {
let { query, results } = useLoaderData();
let submit = useSubmit();
let debounceRef = useRef(null);
function handleChange(event) {
let form = event.currentTarget;
clearTimeout(debounceRef.current);
debounceRef.current = setTimeout(() => {
submit(form, { replace: true });
}, 300);
}
return (
<Form method="get" onChange={handleChange}>
<input type="search" name="q" defaultValue={query} placeholder="Search contacts..." />
<ul>
{results.map((name) => <li key={name}>{name}</li>)}
</ul>
</Form>
);
}Every keystroke changes the input, but handleChange only calls submit() after 300ms of silence, and replace: true means each search updates the current history entry instead of piling up a new one per query. Because the <input> uses defaultValue rather than value, the browser keeps handling keystrokes natively while the query string and the loader catch up in the background.