--- title: useFetchers new: true --- # `useFetchers` Returns an array of all inflight [fetchers][usefetcher] without their `load`, `submit`, or `Form` properties (can't have parent components trying to control the behavior of their children! We know from IRL experience that this is a fool's errand.) This feature only works if using a data router, see [Picking a Router][pickingarouter] ```tsx import { useFetchers } from "react-router-dom"; function SomeComp() { const fetchers = useFetchers(); // array of inflight fetchers } ``` This is useful for components throughout the app that didn't create the fetchers but want to use their submissions to participate in optimistic UI. For example, imagine a UI where the sidebar lists projects, and the main view displays a list of checkboxes for the current project. The sidebar could display the number of completed and total tasks for each project. ``` +-----------------+----------------------------+ | | | | Soccer (8/9) | [x] Do the dishes | | | | | > Home (2/4) | [x] Fold laundry | | | | | | [ ] Replace battery in the | | | smoke alarm | | | | | | [ ] Change lights in kids | | | bathroom | | | | +-----------------+----------------------------┘ ``` When the user clicks a checkbox, the submission goes to the action to change the state of the task. Instead of creating a "loading state" we want to create an "optimistic UI" that will **immediately** update the checkbox to appear checked even though the server hasn't processed it yet. In the checkbox component, we can use `fetcher.formData`: ```tsx function Task({ task }) { const { projectId, id } = task; const toggle = useFetcher(); const checked = toggle.formData ? toggle.formData.get("complete") === "on" : task.complete; return ( ); } ``` This awesome for the checkbox, but the sidebar will say 2/4 while the checkboxes show 3/4 when the user clicks on of them! ``` +-----------------+----------------------------+ | | | | Soccer (8/9) | [x] Do the dishes | | | | | > Home (2/4) | [x] Fold laundry | | WRONG! ^ | | | CLICK!-->[x] Replace battery in the | | | smoke alarm | | | | | | [ ] Change lights in kids | | | bathroom | | | | +-----------------+----------------------------┘ ``` Because routes are automatically revalidated, the sidebar will quickly update and be correct. But for a moment, it's gonna feel a little funny. This is where `useFetchers` comes in. Up in the sidebar, we can access all the inflight fetcher states from the checkboxes - even though it's not the component that created them. The strategy has three steps: 1. Find the submissions for tasks in a specific project 2. Use the `fetcher.formData` to immediately update the count 3. Use the normal task's state if it's not inflight ```tsx function ProjectTaskCount({ project }) { let completedTasks = 0; const fetchers = useFetchers(); // Find this project's fetchers const relevantFetchers = fetchers.filter((fetcher) => { return fetcher.formAction?.startsWith( `/projects/${project.id}/tasks/` ); }); // Store in a map for easy lookup const myFetchers = new Map( relevantFetchers.map(({ formData }) => [ formData.get("id"), formData.get("complete") === "on", ]) ); // Increment the count for (const task of project.tasks) { if (myFetchers.has(task.id)) { if (myFetchers.get(task.id)) { // if it's being submitted, increment optimistically completedTasks++; } } else if (task.complete) { // otherwise use the real task's data completedTasks++; } } return ( {completedTasks}/{project.tasks.length} ); } ``` It's a little bit of work, but it's mostly just asking React Router for the state it's tracking and doing an optimistic calculation based on it. [usefetcher]: ./use-fetcher [pickingarouter]: ../routers/picking-a-router