简化的Reaction组件,形式如下:

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 路由操作功能:

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;

除了Reaction Hook isSubmitting之外,所有内容都是一样的,它仍然是未订阅和错误/未定义的.如果我try 使用RTK,则与来自Const [_createItem, { isLoading, error }] = useCreateItemMutation();isLoading, error相同.

UseForm数据成功地通过handleSubmit和RRv6 submit提交-该操作成功地获取了具有正确Zod架构类型ItemFormFieldsformData.RTKMutations 启动器成功地将一个新实体发布到数据库,然后数据库在响应中返回ID,并且RRv6redirect发生,其中它成功地通过加载器获取并且一切顺利……除了useForm中的isSubmitting个FormState仍然是未订阅的,RTK的isLoding也是未定义的.在常规的异步示例中,isSubmitting的工作方式与预期相同,但一旦它从Reaction路由6从useSubmit转到actionvoid submit,它就永远不会订阅,也永远不会更新到true.我假设它需要Promise,但RR6的S useSubmit提供了一个常规的空白,并在幕后完成了整个流程.

编辑:只需普通的Reaction路由6(浏览器数据路由),具有/new的路由:

{
  path: 'new',
  element: <NewItem />,
  action: itemsAction
}

推荐答案

useForm‘S formState.isSubmitting仅为真while提交处理程序函数正在执行,并且由于它是一个同步函数并且只执行两行,因此它只在非常短的持续时间内提交.由于路由操作正在执行工作,因此您需要将操作处理排队以呈现任何加载的UI.您可以通过Reaction-Router-DOM中的useNavigation钩子访问它.

navigation.state:

  • idle-没有导航挂起.
  • submitting-由于使用POSTPUTPATCHDELETE提交表单而调用路由操作
  • loading-正在调用下一路由的加载器以呈现下一页

使用POSTPUTPATCHDELETE转换的表单提交 通过这些州:

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>
);

Typescript相关问答推荐

无需刷新即可让现场访客逆变

创建一个通用上下文,允许正确推断其中的子项

React组件数组及其props 的类型定义

根据另一个成员推断类成员的类型

如何正确修复TypScript 4.7到4.8升级中的TS 2345编译错误

为什么typescript不能推断类的方法返回类型为泛型''

具有泛型类方法的类方法修饰符

角效用函数的类型推断

我可以使用TypeScrip从字符串词典/记录中填充强类型的环境对象吗?

如何从扩展接口推断泛型?

ANGLING v17 CLI默认设置为独立:延迟加载是否仍需要@NgModule进行路由?

如何合并两个泛型对象并获得完整的类型安全结果?

如何从输入中删除值0

从route.参数获取值.订阅提供";无法读取未定义";的属性

在构建Angular 应用程序时,有没有办法通过CLI传递参数?

如何为带有参数的函数类型指定类型保护?

为什么`map()`返回类型不能维护与输入相同数量的值?

可选通用映射器函数出错

带有 Select 器和映射器的Typescript 泛型

如何在没有空接口的情况下实现求和类型功能?