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

Next.js 14 + NextUI:表单提交成功后关闭模态框求助

解决Next.js 14 + NextUI模态框表单提交后关闭问题

核心思路

服务端Action无法直接调用客户端的模态框关闭方法,我们可以利用useFormState返回的状态变化,在客户端监听提交成功的信号,触发模态框关闭。

具体修改步骤

1. 修复模态框内部onClose命名冲突

你的代码中ModalContent回调的onClose参数与组件自身useDisclosure的onClose重名,会导致逻辑混淆,先修改内部命名:

<ModalContent>
  {/* 把参数名改成handleModalClose,避免和组件的onClose冲突 */}
  {(handleModalClose) => (
    <>
      <form action={formAction}>
        {/* ... 其他表单内容 ... */}
        <ModalFooter>
          {state?.message && <Code>{state.message}</Code>}
          <Button color="danger" variant="light" onPress={handleModalClose}>
            Close
          </Button>
          <SubmitButton />
        </ModalFooter>
      </form>
    </>
  )}
</ModalContent>

2. 监听提交成功状态,触发模态框关闭

导入useEffect,监听useFormState返回的state,当状态为成功的候选对象(无错误信息)时,调用组件的onClose方法:

// 先导入useEffect
import React, { useEffect } from "react";

// 在组件内部添加监听逻辑
export default function AddCandidateModal({ campaign_id, status }: { campaign_id: number, status: CandidateStatusType[] }) {
  const { isOpen, onOpen, onOpenChange, onClose } = useDisclosure();
  const { register } = useForm()
  const [state, formAction] = useFormState(createCandidateAction, null)

  // 监听state变化,提交成功则关闭模态框
  useEffect(() => {
    // 当state存在且没有错误message时,判定为提交成功
    if (state && !state.message) {
      onClose();
    }
  }, [state, onClose]);

  // ... 其余代码不变
}

3. 可选:保留redirect逻辑的处理

如果需要在提交成功后跳转页面,redirect会触发页面刷新,此时模态框的isOpen状态会自动重置为初始的false,但这种方式会刷新页面,体验不如无刷新关闭流畅,推荐优先使用第一种方案。

修改后的完整AddCandidateModal.tsx

import React, { useEffect } from "react";
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button, useDisclosure, Input, Code, Select, SelectItem } from "@nextui-org/react";
import { PlusIcon } from "./PlusIcon";
import { useFormState, useFormStatus } from "react-dom";
import { useForm } from "react-hook-form";
import { createCandidateAction } from "@/app/actions/candidate-action";
import { CandidateStatusType } from "@/types/campaign";

function SubmitButton() {
    const { pending } = useFormStatus()
    return (
        <Button color="primary" aria-disabled={pending} type="submit" isLoading={pending}>Save</Button>
    )
}

export default function AddCandidateModal({ campaign_id, status }: { campaign_id: number, status: CandidateStatusType[] }) {
    const { isOpen, onOpen, onOpenChange, onClose } = useDisclosure();
    const { register } = useForm()
    const [state, formAction] = useFormState(createCandidateAction, null)

    useEffect(() => {
        if (state && !state.message) {
            onClose();
        }
    }, [state, onClose]);

    return (
        <>
            <Button onPress={onOpen} color="primary" endContent={<PlusIcon />}>Add New</Button>
            <Modal isOpen={isOpen}  onOpenChange={onOpenChange}>
                <ModalContent>
                    {(handleModalClose) => (
                        <>
                            <form 
                                action={formAction}
                            >
                                <ModalHeader className="flex flex-col gap-1">New Candidate</ModalHeader>
                                <ModalBody>
                                    <Select
                                        items={status}
                                        label="Candidate Status"
                                        placeholder="Select a status"
                                        className=""
                                        isRequired
                                        {...register('status')}
                                    >
                                        {(item) => <SelectItem key={item.id}>{item.name}</SelectItem>}
                                    </Select>
                                    <div className="grid grid-cols-2 gap-3">
                                        <Input isRequired label="First Name" placeholder="Travis" {...register('first_name')} />
                                        <Input isRequired label="Last Name" placeholder="Scott" {...register('last_name')} />
                                    </div>
                                    <Input isRequired type="email" label="Email" placeholder="scott@tra.org" {...register('email')} />
                                    <Input isRequired type="phone" label="Phone" placeholder="123 45678" {...register('phone_number')} />
                                    <span className="text-gray-500">Upload Resume</span>
                                    <input className=" w-full py-3 px-3 text-gray-500 rounded-lg cursor-pointer bg-gray-100 " type="file" />
                                    <input type="hidden" value={campaign_id} {...register("campaign")} />
                                </ModalBody>
                                <ModalFooter>
                                    {state?.message && <Code>{state.message}</Code>}
                                    <Button color="danger" variant="light" onPress={handleModalClose}>
                                        Close
                                    </Button>
                                    <SubmitButton />
                                </ModalFooter>
                            </form>
                        </>
                    )}
                </ModalContent>
            </Modal>
        </>
    );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 05:22:05