带有表单的简化 React 组件:
const itemSchema = z.object({
title: z.string().max(50),
});
type ItemFormFields = z.infer<typeof itemSchema>;
const {
register,
handleSubmit,
reset,
setError,
formState: { isSubmitting, errors }
} = useForm<ItemFormFields>({
defaultValues: {
title: "title placehnolder"
},
resolver: zodResolver(itemSchema)
});
// RTK's isLoading and error remain not updated for mutation hooks
// but the same work as expected with query hooks done through .initialize() in RRv6 loader
// const [_createItem, { isLoading, error }] = useCreateItemMutation();
const submit = useSubmit();
const actionData = useActionData<typeof itemsAction>();
const hasError = actionData && 'error' in actionData && isString(actionData.error);
useEffect(() => {
if (hasError) {
const { error } = actionData;
setError('root', {
message: error
});
}
}, [actionData, hasError, setError]);
return (
<div className="new-item">
<Form
onSubmit={(event: React.FormEvent<HTMLFormElement>) => {
handleSubmit((data: ItemFormFields) => {
submit(data, { method: 'post' });
reset();
})(event);
}}
>
<div className="new-item__form-body">
<div>
<div>
<label htmlFor="title">Title:</label>
</div>
{errors.title && <div>{errors.title.message}</div>}
</div>
<div>
<div>
<input
id="title"
type="text"
required
{...register('title')}
/>
</div>
</div>
</div>
<div className="new-item__form-footer">
<Button
disabled={isSubmitting}
type="submit"
>
{isSubmitting ? 'Loading...' : 'Submit'}
</Button>
</div>
{errors.root && <div>{errors.root.message}</div>}
</Form>
</div>
);
React Router Action 函数:
import { ActionFunction, ActionFunctionArgs, redirect } from 'react-router-dom';
// ...
const fromEntries = <T extends object>(data: FormData) => Object.fromEntries<string | number | File>(data) as T;
export const itemsAction = (async ({ request }: ActionFunctionArgs) => {
const data: FormData = await request.formData();
const formData: ItemFormFields = fromEntries(data);
const mutation = store.dispatch(api.endpoints.createItem.initiate(formData));
try {
const { id } = await mutation.unwrap();
return redirect(`/items/${id}`);
} catch (error) {
return {
error: 'Post-Validation error message placeholder'
};
}
}) satisfies ActionFunction;
除了 React Hook
isSubmitting
之外,一切都正常工作,它仍然是未订阅且 false/未定义的。如果我尝试使用 RTK,则与 const isLoading, error
中的 [_createItem, { isLoading, error }] = useCreateItemMutation();
相同。
useForm 数据已通过
handleSubmit
和 RRv6 submit
成功提交 - 该操作成功使用正确的 Zod 架构类型 formData
获取 ItemFormFields
。 RTK 突变启动成功将一个新实体发布到数据库,然后在响应中返回 id,并且 RRv6 redirect
发生在它成功通过加载器获取并且一切顺利的情况下......除了 中的
isSubmitting
formState 之外useForm
仍然未订阅,RTK 的 isLoading 也仍然未定义。 isSubmitting
在常规异步示例中按预期工作,但是一旦它从 React Router 6 的 action
通过 void submit
到达 useSubmit
- 它就永远不会订阅也永远不会更新到 true
。我认为它需要一个 Promise
,但 RRv6 的 useSubmit
提供了一个常规的空白,并在场景后面完成整个流程。
编辑:只是常规的 React Router 6(浏览器数据路由器),带有 /new 的路由:
{
path: 'new',
element: <NewItem />,
action: itemsAction
}
useForm
的formState.isSubmitting
值仅在提交处理函数正在执行时才为true,并且由于它是一个同步函数并且仅执行两行,因此仅在很短的时间内提交。由于路由操作正在执行这项工作,因此您需要对操作处理进行排队以呈现任何加载 UI。您可以通过 React-Router-DOM 中的 useNavigation
钩子访问它。参见navigation.state
idle
- - 没有待处理的导航。
submitting- - 由于使用
loadingPOST
、、PUT
或PATCH
DELETE
提交表单而调用路由操作- - 正在调用下一个路由的加载器来渲染下一页
使用POST
、
、PUT
或PATCH
过渡进行表单提交 通过这些状态:DELETE
idle → submitting → loading → idle
您可以检查当前操作是否处于空闲状态:
const navigation = useNavigation();
const isSubmitting = navigation.state !== "idle";
...
<Button disabled={isSubmitting} type="submit">
{isSubmitting ? "Loading..." : "Submit"}
</Button>
示例:
import {
useActionData,
useSubmit,
useNavigation,
} from "react-router-dom";
...
const {
register,
handleSubmit,
reset,
setError,
formState: { errors }
} = useForm<ItemFormFields>({
defaultValues: {
title: "title placehnolder"
}
resolver: zodResolver(itemSchema)
});
const submit = useSubmit();
const actionData = useActionData<typeof itemsAction>();
const navigation = useNavigation();
const isSubmitting = navigation.state !== "idle";
...
return (
<div className="new-item">
<Form
onSubmit={handleSubmit((data) => {
submit(data, { method: "post" });
reset();
})}
>
<div className="new-item__form-body">
<div>
<div>
<label htmlFor="title">Title:</label>
</div>
{errors.title && <div>{errors.title.message}</div>}
</div>
<div>
<div>
<input id="title" type="text" required {...register("title")} />
</div>
</div>
</div>
<div className="new-item__form-footer">
<Button disabled={isSubmitting} type="submit">
{navigation.state !== "idle" ? "Loading..." : "Submit"}
</Button>
</div>
{errors.root && <div>{errors.root.message}</div>}
</Form>
</div>
);