useFetcher
In HTML/HTTP, data mutations and loads are modeled with navigation: <a href>
and <form action>
. Both cause a navigation in the browser. The React Router equivalents are <Link>
and <Form>
.
But sometimes you want to call a loader outside of navigation, or call an action (and get the data on the page to revalidate) without changing the URL. Or you need to have multiple mutations in-flight at the same time.
Many interactions with the server aren't navigation events. This hook lets you plug your UI into your actions and loaders without navigating.
This is useful when you need to:
If you're building a highly interactive, "app like" user interface, you will useFetcher
often.
import { useFetcher } from "react-router-dom";
function SomeComponent() {
const fetcher = useFetcher();
// call submit or load in a useEffect
React.useEffect(() => {
fetcher.submit(data, options);
fetcher.load(href);
}, [fetcher]);
// build your UI with these properties
fetcher.state;
fetcher.formData;
fetcher.formMethod;
fetcher.formAction;
fetcher.data;
// render a form that doesn't cause navigation
return <fetcher.Form />;
}
Fetchers have a lot of built-in behavior:
errorElement
(just like a normal navigation from <Link>
or <Form>
)<Link>
or <Form>
)fetcher.state
You can know the state of the fetcher with fetcher.state
. It will be one of:
fetcher.load
) or is being revalidated after a separate submission or useRevalidator
callfetcher.Form
Just like <Form>
except it doesn't cause a navigation. (You'll get over the dot in JSX ... we hope!)
function SomeComponent() {
const fetcher = useFetcher();
return (
<fetcher.Form method="post" action="/some/route">
<input type="text" />
</fetcher.Form>
);
}
fetcher.load()
Loads data from a route loader.
import { useFetcher } from "react-router-dom";
function SomeComponent() {
const fetcher = useFetcher();
useEffect(() => {
if (fetcher.state === "idle" && !fetcher.data) {
fetcher.load("/some/route");
}
}, [fetcher]);
return <div>{fetcher.data || "Loading..."}</div>;
}
Although a URL might match multiple nested routes, a fetcher.load()
call will only call the loader on the leaf match (or parent of index routes).
If you find yourself calling this function inside of click handlers, you can probably simplify your code by using <fetcher.Form>
instead.
fetcher.load
calls that are active on the page will be re-executed as part of revalidation (either after a navigation submission, another fetcher submission, or a useRevalidator()
call)
fetcher.submit()
The imperative version of <fetcher.Form>
. If a user interaction should initiate the fetch, you should use <fetcher.Form>
. But if you, the programmer are initiating the fetch (not in response to a user clicking a button, etc.), then use this function.
For example, you may want to log the user out after a certain amount of idle time:
import { useFetcher } from "react-router-dom";
import { useFakeUserIsIdle } from "./fake/hooks";
export function useIdleLogout() {
const fetcher = useFetcher();
const userIsIdle = useFakeUserIsIdle();
useEffect(() => {
if (userIsIdle) {
fetcher.submit(
{ idle: true },
{ method: "post", action: "/logout" }
);
}
}, [userIsIdle]);
}
If you want to submit to an index route, use the ?index
param.
If you find yourself calling this function inside of click handlers, you can probably simplify your code by using <fetcher.Form>
instead.
fetcher.data
The returned data from the loader or action is stored here. Once the data is set, it persists on the fetcher even through reloads and resubmissions.
function ProductDetails({ product }) {
const fetcher = useFetcher();
return (
<details
onToggle={(event) => {
if (
event.currentTarget.open &&
fetcher.state === "idle" &&
!fetcher.data
) {
fetcher.load(`/product/${product.id}/details`);
}
}}
>
<summary>{product.name}</summary>
{fetcher.data ? (
<div>{fetcher.data}</div>
) : (
<div>Loading product details...</div>
)}
</details>
);
}
fetcher.formData
When using <fetcher.Form>
or fetcher.submit()
, the form data is available to build optimistic UI.
function TaskCheckbox({ task }) {
let fetcher = useFetcher();
// while data is in flight, use that to immediately render
// the state you expect the task to be in when the form
// submission completes, instead of waiting for the
// network to respond. When the network responds, the
// formData will no longer be available and the UI will
// use the value in `task.status` from the revalidation
let status =
fetcher.formData?.get("status") || task.status;
let isComplete = status === "complete";
return (
<fetcher.Form method="post">
<button
type="submit"
name="status"
value={isComplete ? "incomplete" : "complete"}
>
{isComplete ? "Mark Incomplete" : "Mark Complete"}
</button>
</fetcher.Form>
);
}
fetcher.formAction
Tells you the action url the form is being submitted to.
<fetcher.Form action="/mark-as-read" />;
// when the form is submitting
fetcher.formAction; // "mark-as-read"
fetcher.formMethod
Tells you the method of the form being submitted: get, post, put, patch, or delete.
<fetcher.Form method="post" />;
// when the form is submitting
fetcher.formMethod; // "post"