You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React-Hook-Form模态框确认后无法直接提交的问题求助

React Hook Form 提交确认模态框无法触发表单提交问题

问题场景

点击表单提交按钮后弹出确认模态框,用户点击确认后无法直接提交表单,必须关闭模态框后再次点击提交按钮才能完成提交。尝试将模态框放在FormSubmitButton组件及表单组件内部均未解决。

相关组件代码

FormSubmitButton组件

import { Svg } from "@/components/Svg";
import { Box, Button, ButtonProps } from "@chakra-ui/react";

import type { FieldValues, FormState } from "react-hook-form";

export type FormSubmitButtonProps<FV extends FieldValues> = ButtonProps & {
  formState: FormState<FV>;
  folder?: string;
  iconName?: string;
  wide?: boolean;
};

const FormSubmitButton = <FV extends FieldValues>({
  folder,
  iconName,
  name,
  formState,
  wide,
  ...props
}: FormSubmitButtonProps<FV>) => {
  const { isSubmitting } = formState;

  return (
    <Button
      type="submit"
      isLoading={isSubmitting}
      isDisabled={isSubmitting}
      {...props}
    >
      {!wide && folder && iconName && (
        <Box mr="8px">
          <Svg folder={folder} iconName={iconName} />
        </Box>
      )}
      {name}
      {wide && folder && iconName && (
        <Box ml="8px">
          <Svg folder={folder} iconName={iconName} />
        </Box>
      )}
    </Button>
  );
};

export default FormSubmitButton;

用户表单组件

import { FC, useState } from "react";
import { VStack, HStack, Stack } from "@chakra-ui/react";
import { IconButton } from "@/components/IconButton";
import { useFormContext } from "react-hook-form";
import { createInputList } from "@/components/form";
import { LeftFormSection } from "./LeftFormSection";
import { RightFormSection } from "./RightFormSection";
import { UserSchemaType } from "../NewUser";
import { useParams } from "react-router";
import { useAccountPasswordCreate } from "@/api/queries/account";
import { ConfirmationModal } from "@/components/ConfirmationModal";

const { FormSubmitButton } = createInputList<UserSchemaType>();

export const UserForm: FC = () => {
  const { formState, reset, getValues } = useFormContext<UserSchemaType>();
  const { id: UserId } = useParams<{ id: string }>();
  const { isDirty } = formState;

  const [showClearConfirmation, setShowClearConfirmation] = useState(false);
  const [showPasswordConfirmation, setShowPasswordConfirmation] = useState(false);

  const { mutate: passwordCreate } = useAccountPasswordCreate();

  const email = getValues("email");

  const handleNewPassword = () => {
    passwordCreate({ data: { email } });
  };

  const handleResetForm = () => {
    reset();
  };
  const handleClearConfirmation = () => {
    setShowClearConfirmation(true);
  };
  const handleClearConfirmationCancel = () => {
    setShowClearConfirmation(false);
  };
  const handleClearConfirmationConfirm = () => {
    handleResetForm();
  };

  const handlePasswordConfirmationConfirm = async () => {
    handleNewPassword();
    setShowPasswordConfirmation(false);
  };

  const handlePasswordConfirmation = () => {
    setShowPasswordConfirmation(true);
  };
  const handlePasswordConfirmationCancel = () => {
    setShowPasswordConfirmation(false);
  };

  return (
    <VStack
      bg="white"
      borderRadius="24px"
      w={{ base: "full", md: "1130px" }}
      maxW={{ base: "full", md: "1130px" }}
      p="40px"
    >
      <Stack
        gap={4}
        alignItems="flex-start"
        w="full"
        flexDir={{ base: "column", md: "row" }}
      >
        <LeftFormSection />
        <RightFormSection />
      </Stack>
      <HStack
        data-testid="form-footer"
        w="full"
        justifyContent="flex-end"
        pt={4}
      >
        <IconButton
          name="Zrušit"
          folder="menu"
          iconName="close"
          variant="outline"
          colorScheme="darkGray"
          onClick={handleClearConfirmation}
        />
        {UserId && (
          <IconButton
            folder="menu"
            iconName="edit"
            colorScheme="darkGray"
            variant="outline"
            name="Nové heslo"
            onClick={handlePasswordConfirmation}
          />
        )}
        <FormSubmitButton
          name="Uložit"
          iconName="ulozhit"
          folder="menu"
          formState={formState}
          isDisabled={!isDirty}
        />
      </HStack>
      <ConfirmationModal
        isOpen={showPasswordConfirmation}
        onClose={handlePasswordConfirmationCancel}
        onConfirm={handlePasswordConfirmationConfirm}
        title="Jste si jisti, že chcete změnit heslo?"
        message="Tímto aktem změníte heslo uživatele. Jste si jisti?"
        confirmButtonText="Ano, změnit heslo"
        cancelButtonText="Zrušit"
      />
      <ConfirmationModal
        isOpen={showClearConfirmation}
        onClose={handleClearConfirmationCancel}
        onConfirm={handleClearConfirmationConfirm}
        title="Jasné potvrzení formuláře"
        message="Jste si jisti, že chcete formulář vymazat?"
        confirmButtonText="Ano, čirá"
        cancelButtonText="Zrušit"
      />
    </VStack>
  );
};

ConfirmationModal组件

import {
  Button,
  Modal,
  ModalBody,
  ModalContent,
  ModalFooter,
  ModalHeader,
  ModalOverlay,
} from "@chakra-ui/react";
import { FC } from "react";

interface ConfirmationModalProps {
  isOpen: boolean;
  onClose: VoidFunction;
  onConfirm: VoidFunction;
  title: string;
  message: string;
  confirmButtonText: string;
  cancelButtonText: string;
}

export const ConfirmationModal: FC<ConfirmationModalProps> = ({
  isOpen,
  onClose,
  onConfirm,
  title,
  message,
  confirmButtonText,
  cancelButtonText,
}) => {
  const handleConfirm = () => {
    onConfirm();
    onClose();
  };

  return (
    <Modal isOpen={isOpen} onClose={onClose}>
      <ModalOverlay />
      <ModalContent>
        <ModalHeader>{title}</ModalHeader>
        <ModalBody>{message}</ModalBody>
        <ModalFooter>
          <Button colorScheme="red" mr={3} onClick={handleConfirm}>
            {confirmButtonText}
          </Button>
          <Button variant="ghost" onClick={onClose}>
            {cancelButtonText}
          </Button>
        </ModalFooter>
      </ModalContent>
    </Modal>
  );
};

问题原因

提交按钮是原生type="submit"按钮,点击后直接触发表单默认提交流程,但弹出模态框中断了该流程;同时模态框确认时没有手动触发表单提交动作,导致第一次点击仅弹出模态框,未执行提交逻辑,第二次点击时模态框已关闭,才触发默认提交。

解决方案

1. 核心修改:拦截默认提交,手动触发提交逻辑

修改用户表单组件,添加提交确认模态框状态,拦截提交按钮的默认行为,在模态框确认后手动执行表单提交:

import { FC, useState } from "react";
import { VStack, HStack, Stack } from "@chakra-ui/react";
import { IconButton } from "@/components/IconButton";
import { useFormContext } from "react-hook-form";
import { createInputList } from "@/components/form";
import { LeftFormSection } from "./LeftFormSection";
import { RightFormSection } from "./RightFormSection";
import { UserSchemaType } from "../NewUser";
import { useParams } from "react-router";
import { useAccountPasswordCreate } from "@/api/queries/account";
import { ConfirmationModal } from "@/components/ConfirmationModal";

const { FormSubmitButton } = createInputList<UserSchemaType>();

export const UserForm: FC = () => {
  const { formState, reset, getValues, handleSubmit } = useFormContext<UserSchemaType>();
  const { id: UserId } = useParams<{ id: string }>();
  const { isDirty, isSubmitting } = formState;

  const [showClearConfirmation, setShowClearConfirmation] = useState(false);
  const [showPasswordConfirmation, setShowPasswordConfirmation] = useState(false);
  // 新增提交确认模态框状态
  const [showSubmitConfirmation, setShowSubmitConfirmation] = useState(false);

  const { mutate: passwordCreate } = useAccountPasswordCreate();

  // 替换为你的实际表单提交逻辑
  const onSubmit = (data: UserSchemaType) => {
    console.log("表单提交数据:", data);
    // 这里添加API调用等提交逻辑
  };

  const email = getValues("email");

  const handleNewPassword = () => {
    passwordCreate({ data: { email } });
  };

  const handleResetForm = () => {
    reset();
  };
  const handleClearConfirmation = () => {
    setShowClearConfirmation(true);
  };
  const handleClearConfirmationCancel = () => {
    setShowClearConfirmation(false);
  };
  const handleClearConfirmationConfirm = () => {
    handleResetForm();
    setShowClearConfirmation(false);
  };

  const handlePasswordConfirmationConfirm = async () => {
    handleNewPassword();
    setShowPasswordConfirmation(false);
  };

  const handlePasswordConfirmation = () => {
    setShowPasswordConfirmation(true);
  };
  const handlePasswordConfirmationCancel = () => {
    setShowPasswordConfirmation(false);
  };

  // 点击提交按钮:弹出确认模态框
  const handleSubmitClick = () => {
    setShowSubmitConfirmation(true);
  };

  // 确认提交:手动触发表单提交
  const handleSubmitConfirm = () => {
    handleSubmit(onSubmit)();
    setShowSubmitConfirmation(false);
  };

  const handleSubmitCancel = () => {
    setShowSubmitConfirmation(false);
  };

  return (
    <VStack
      bg="white"
      borderRadius="24px"
      w={{ base: "full", md: "1130px" }}
      maxW={{ base: "full", md: "1130px" }}
      p="40px"
    >
      <Stack
        gap={4}
        alignItems="flex-start"
        w="full"
        flexDir={{ base: "column", md: "row" }}
      >
        <LeftFormSection />
        <RightFormSection />
      </Stack>
      <HStack
        data-testid="form-footer"
        w="full"
        justifyContent="flex-end"
        pt={4}
      >
        <IconButton
          name="Zrušit"
          folder="menu"
          iconName="close"
          variant="outline"
          colorScheme="darkGray"
          onClick={handleClearConfirmation}
        />
        {UserId && (
          <IconButton
            folder="menu"
            iconName="edit"
            colorScheme="darkGray"
            variant="outline"
            name="Nové heslo"
            onClick={handlePasswordConfirmation}
          />
        )}
        {/* 修改提交按钮:改为button类型,绑定自定义点击事件 */}
        <FormSubmitButton
          name="Uložit"
          iconName="ulozhit"
          folder="menu"
          formState={formState}
          isDisabled={!isDirty || isSubmitting}
          onClick={handleSubmitClick}
          type="button"
        />
      </HStack>
      {/* 新增提交确认模态框 */}
      <ConfirmationModal
        isOpen={showSubmitConfirmation}
        onClose={handleSubmitCancel}
        onConfirm={handleSubmitConfirm}
        title="Potvrzení odeslání formuláře"
        message="Jste si jisti, že chcete uložit změny?"
        confirmButtonText="Ano, uložit"
        cancelButtonText="Zrušit"
      />
      <ConfirmationModal
        isOpen={showPasswordConfirmation}
        onClose={handlePasswordConfirmationCancel}
        onConfirm={handlePasswordConfirmationConfirm}
        title="Jste si jisti, že chcete změnit heslo?"
        message="Tímto aktem změníte heslo uživatele. Jste si jisti?"
        confirmButtonText="Ano, změnit heslo"
        cancelButtonText="Zrušit"
      />
      <ConfirmationModal
        isOpen={showClearConfirmation}
        onClose={handleClearConfirmationCancel}
        onConfirm={handleClearConfirmationConfirm}
        title="Jasné potvrzení formuláře"
        message="Jste si jisti, že chcete formulář vymazat?"
        confirmButtonText="Ano, čirá"
        cancelButtonText="Zrušit"
      />
    </VStack>
  );
};

2. 可选优化:自动切换按钮类型

修改FormSubmitButton组件,当传递自定义onClick事件时自动切换为button类型,无需手动指定:

import { Svg } from "@/components/Svg";
import { Box, Button, ButtonProps } from "@chakra-ui/react";

import type { FieldValues, FormState } from "react-hook-form";

export type FormSubmitButtonProps<FV extends FieldValues> = ButtonProps & {
  formState: FormState<FV>;
  folder?: string;
  iconName?: string;
  wide?: boolean;
};

const FormSubmitButton = <FV extends FieldValues>({
  folder,
  iconName,
  name,
  formState,
  wide,
  onClick,
  ...props
}: FormSubmitButtonProps<FV>) => {
  const { isSubmitting } = formState;

  return (
    <Button
      type={onClick ? "button" : "submit"}
      isLoading={isSubmitting}
      isDisabled={isSubmitting}
      onClick={onClick}
      {...props}
    >
      {!wide && folder && iconName && (
        <Box mr="8px">
          <Svg folder={folder} iconName={iconName} />
        </Box>
      )}
      {name}
      {wide && folder && iconName && (
        <Box ml="8px">
          <Svg folder={folder} iconName={iconName} />
        </Box>
      )}
    </Button>
  );
};

export default FormSubmitButton;

内容的提问来源于stack exchange,提问作者krzhkv

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 11:25:14