React Router 6 操作未从 React Hook Form 订阅 isSubmitting + 也不使用 RTK 查询突变

问题描述 投票:0回答:1

带有表单的简化 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
}
reactjs typescript react-router-dom react-hook-form
1个回答
0
投票

useForm
formState.isSubmitting
值仅在提交处理函数正在执行时才为true,并且由于它是一个同步函数并且仅执行两行,因此仅在很短的时间内提交。由于路由操作正在执行这项工作,因此您需要对操作处理进行排队以呈现任何加载 UI。您可以通过 React-Router-DOM 中的 useNavigation
 钩子访问它。
参见

navigation.state

    idle
  • - 没有待处理的导航。
  • submitting
  • - 由于使用 POST
    PUT
    PATCH
    DELETE
     提交表单而调用路由操作
    
  • loading
  • - 正在调用下一个路由的加载器来渲染下一页
  • 使用
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> );

© www.soinside.com 2019 - 2024. All rights reserved.