--- title: Using Fetchers --- # Using Fetchers [MODES: framework, data]

Fetchers are useful for creating complex, dynamic user interfaces that require multiple, concurrent data interactions without causing a navigation. Fetchers track their own, independent state and can be used to load data, mutate data, submit forms, and generally interact with loaders and actions. ## Calling Actions The most common case for a fetcher is to submit data to an action, triggering a revalidation of route data. Consider the following route module: ```tsx import { useLoaderData } from "react-router"; export async function clientLoader({ request }) { let title = localStorage.getItem("title") || "No Title"; return { title }; } export default function Component() { let data = useLoaderData(); return (

{data.title}

); } ``` ### 1. Add an action First we'll add an action to the route for the fetcher to call: ```tsx lines=[7-11] import { useLoaderData } from "react-router"; export async function clientLoader({ request }) { // ... } export async function clientAction({ request }) { await new Promise((res) => setTimeout(res, 1000)); let data = await request.formData(); localStorage.setItem("title", data.get("title")); return { ok: true }; } export default function Component() { let data = useLoaderData(); // ... } ``` ### 2. Create a fetcher Next create a fetcher and render a form with it: ```tsx lines=[7,12-14] import { useLoaderData, useFetcher } from "react-router"; // ... export default function Component() { let data = useLoaderData(); let fetcher = useFetcher(); return (

{data.title}

); } ``` ### 3. Submit the form If you submit the form now, the fetcher will call the action and revalidate the route data automatically. ### 4. Render pending state Fetchers make their state available during the async work so you can render pending UI the moment the user interacts: ```tsx lines=[10] export default function Component() { let data = useLoaderData(); let fetcher = useFetcher(); return (

{data.title}

{fetcher.state !== "idle" &&

Saving...

}
); } ``` ### 5. Optimistic UI Sometimes there's enough information in the form to render the next state immediately. You can access the form data with `fetcher.formData`: ```tsx lines=[3-4,8] export default function Component() { let data = useLoaderData(); let fetcher = useFetcher(); let title = fetcher.formData?.get("title") || data.title; return (

{title}

{fetcher.state !== "idle" &&

Saving...

}
); } ``` ### 6. Fetcher Data and Validation Data returned from an action is available in the fetcher's `data` property. This is primarily useful for returning error messages to the user for a failed mutation: ```tsx lines=[7-10,28-32] // ... export async function clientAction({ request }) { await new Promise((res) => setTimeout(res, 1000)); let data = await request.formData(); let title = data.get("title") as string; if (title.trim() === "") { return { ok: false, error: "Title cannot be empty" }; } localStorage.setItem("title", title); return { ok: true, error: null }; } export default function Component() { let data = useLoaderData(); let fetcher = useFetcher(); let title = fetcher.formData?.get("title") || data.title; return (

{title}

{fetcher.state !== "idle" &&

Saving...

} {fetcher.data?.error && (

{fetcher.data.error}

)}
); } ``` ## Loading Data Another common use case for fetchers is to load data from a route for something like a combobox. ### 1. Create a search route Consider the following route with a very basic search: ```tsx filename=./search-users.tsx // { path: '/search-users', filename: './search-users.tsx' } const users = [ { id: 1, name: "Ryan" }, { id: 2, name: "Michael" }, // ... ]; export async function loader({ request }) { await new Promise((res) => setTimeout(res, 300)); let url = new URL(request.url); let query = url.searchParams.get("q"); return users.filter((user) => user.name.toLowerCase().includes(query.toLowerCase()), ); } ``` ### 2. Render a fetcher in a combobox component ```tsx import { useFetcher } from "react-router"; export function UserSearchCombobox() { let fetcher = useFetcher(); return (
); } ``` - The action points to the route we created above: "/search-users" - The name of the input is "q" to match the query parameter ### 3. Add type inference ```tsx lines=[2,5] import { useFetcher } from "react-router"; import type { loader } from "./search-users"; export function UserSearchCombobox() { let fetcher = useFetcher(); // ... } ``` Ensure you use `import type` so you only import the types. ### 4. Render the data ```tsx lines=[10-16] import { useFetcher } from "react-router"; export function UserSearchCombobox() { let fetcher = useFetcher(); return (
{fetcher.data && (
    {fetcher.data.map((user) => (
  • {user.name}
  • ))}
)}
); } ``` Note you will need to hit "enter" to submit the form and see the results. ### 5. Render a pending state ```tsx lines=[12-14] import { useFetcher } from "react-router"; export function UserSearchCombobox() { let fetcher = useFetcher(); return (
{fetcher.data && (
    {fetcher.data.map((user) => (
  • {user.name}
  • ))}
)}
); } ``` ### 6. Search on user input Fetchers can be submitted programmatically with `fetcher.submit`: ```tsx lines=[5-7] { fetcher.submit(event.currentTarget.form); }} /> ``` Note the input event's form is passed as the first argument to `fetcher.submit`. The fetcher will use that form to submit the request, reading its attributes and serializing the data from its elements.