useFetchers
返回所有正在进行的 fetchers 数组,不包括它们的 load
、submit
或 Form
属性(不能让父组件试图控制子组件的行为!我们从现实生活中知道,这是徒劳的。)
import { useFetchers } from "react-router-dom";
function SomeComp() {
const fetchers = useFetchers();
// array of inflight fetchers
}
这对于应用程序中未创建 fetchers 但希望使用其提交来参与乐观 UI 的组件很有用。
例如,想象一个 UI,其中侧边栏列出项目,主视图显示当前项目的复选框列表。侧边栏可以显示每个项目的已完成任务数和总任务数。
+-----------------+----------------------------+
| | |
| Soccer (8/9) | [x] Do the dishes |
| | |
| > Home (2/4) | [x] Fold laundry |
| | |
| | [ ] Replace battery in the |
| | smoke alarm |
| | |
| | [ ] Change lights in kids |
| | bathroom |
| | |
+-----------------+----------------------------┘
当用户单击复选框时,提交将转到操作以更改任务的状态。我们不想创建“加载状态”,而是要创建“乐观 UI”,它将**立即**更新复选框以显示已选中状态,即使服务器尚未处理它。在复选框组件中,我们可以使用 fetcher.formData
function Task({ task }) {
const { projectId, id } = task;
const toggle = useFetcher();
const checked = toggle.formData
? toggle.formData.get("complete") === "on"
: task.complete;
return (
<toggle.Form
method="put"
action={`/projects/${projectId}/tasks/${id}`}
>
<input name="id" type="hidden" defaultValue={id} />
<label>
<input
name="complete"
type="checkbox"
checked={checked}
onChange={(e) => toggle.submit(e.target.form)}
/>
</label>
</toggle.Form>
);
}
这对复选框来说很棒,但当用户单击其中一个复选框时,侧边栏会显示 2/4,而复选框会显示 3/4!
+-----------------+----------------------------+
| | |
| 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 |
| | |
+-----------------+----------------------------┘
由于路由会自动重新验证,因此侧边栏将很快更新并正确显示。但有一瞬间,它会感觉有点奇怪。
这就是 useFetchers
的用武之地。在侧边栏中,我们可以访问所有复选框的正在进行的 fetcher 状态 - 即使它不是创建它们的组件。
该策略有三个步骤
fetcher.formData
立即更新计数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 (
<small>
{completedTasks}/{project.tasks.length}
</small>
);
}
这需要一些工作,但主要只是向 React Router 请求它正在跟踪的状态,并根据它进行乐观计算。