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

Next.js中如何通过Server Actions处理下拉按钮并提交表单

Next.js Server Actions表单处理:下拉选框值获取与数据提交修复

问题背景

当前使用Next.js Server Actions处理表单时,其他字段数据可正常获取,但下拉选框的值无法正确传递,且需要提交包含chosenServiceId和chosenServiceName的结构化数据,目标格式如下:

{
    "clientId": "clientId",
    "clientName": "clientName ",
    "clientPhone": "clientPhone",
    "clientStatus": "IN_PROGRESS",
    "chosenServiceId": "5652c359-8400-43f4-bb72-c61dd6a553ed",
    "chosenServiceName": "Sample Service 8",
    "createdAt": "2023-09-13T03:11:31.458Z"
}

问题分析

  1. 下拉选框的option值设置为serviceTitle,导致提交时拿到的是服务名称而非ID,无法匹配目标格式的chosenServiceId
  2. 全局变量clientServiceName无法在Server Actions中可靠使用(Server Actions无状态,全局变量会跨请求污染)
  3. 注释掉的服务列表匹配代码是获取chosenServiceName的关键逻辑,需恢复并正确调用

修复步骤

  • 修正下拉选框的value属性:将option的value改为service.serviceId,保留显示文本为service.serviceTitle,确保提交时获取服务ID
  • 恢复Server Actions中的服务匹配逻辑:通过提交的服务ID从服务列表中匹配对应名称,替代不可靠的全局变量
  • 对齐提交数据字段名:调整字段名与目标格式一致,如将clientServiceId改为chosenServiceId

完整修复代码

import { getServices } from "@/app/services/services/services";
import { redirect } from "next/navigation";
import React from 'react';

async function addClient(data: FormData) {
    'use server';
    // 提取表单数据并做类型断言
    const clientName = data.get("clientName") as string;
    const clientPhone = data.get("clientPhone") as string;
    const clientEmail = data.get("clientEmail") as string;
    const chosenServiceId = data.get("clientService") as string;

    // 校验必填字段
    if (!chosenServiceId) {
        console.error("请选择服务");
        return;
    }

    // 获取服务列表并匹配选中的服务
    const services = await getServices();
    const selectedService = services.find(
        (service: { serviceTitle: string; serviceId: string }) =>
            service.serviceId === chosenServiceId
    );

    if (!selectedService) {
        console.error("选中的服务不存在");
        return;
    }

    // 构造符合要求的提交数据
    const clientData = {
        clientName,
        clientPhone,
        clientEmail,
        clientStatus: "IN_PROGRESS",
        chosenServiceId,
        chosenServiceName: selectedService.serviceTitle,
        // clientId和createdAt若由后端自动生成则无需传入,按需调整
    };

    const apiUrl = "http://localhost:3000/clients";
    const token = process.env.TOKEN;

    try {
        const response = await fetch(apiUrl, {
            method: "POST",
            headers: {
                Authorization: `Bearer ${token}`,
                "Content-Type": "application/json",
            },
            body: JSON.stringify(clientData),
        });

        if (!response.ok) {
            throw new Error(`请求失败:${response.status}`);
        }

        const responseData = await response.json();
        console.log("客户添加成功:", responseData);
        redirect("/"); // 提交成功后跳转至指定页面
    } catch (error) {
        console.error("添加客户出错:", error);
    }
}

export const BookingForm = ({ services }: { services: { serviceTitle: string; serviceId: string }[] }) => {
    return (
        <div className="main-contact-area pb-120">
            <div className="container">
                <div className="section-title">
                    <span>Contact Us</span>
                    <h2>Drop us a message for any query</h2>
                    <p>
                        Lorem ipsum, dolor sit amet consectetur adipisicing elit. Eaque
                        quibusdam deleniti porro praesentium. Aliquam minus quisquam velit
                        in at nam.
                    </p>
                </div>

                <div className="row align-items-center">
                    <div className="col-lg-6 col-md-12">
                        <div className="contact-wrap contact-pages mb-0">
                            <div className="contact-form">
                                <form action={addClient}>
                                    <div className="row">
                                        <div className="col-lg-6 col-sm-6">
                                            <div className="form-group">
                                                <input
                                                    type="text"
                                                    name="clientName"
                                                    placeholder="Name"
                                                    className="form-control"
                                                    id="clientName"
                                                    required
                                                />
                                            </div>
                                        </div>
                                        <div className="col-lg-6 col-sm-6">
                                            <div className="form-group">
                                                <input
                                                    type="text"
                                                    name="clientEmail"
                                                    placeholder="Email"
                                                    className="form-control"
                                                    id="clientEmail"
                                                    required
                                                />
                                            </div>
                                        </div>
                                        <div className="col-lg-6 col-sm-6">
                                            <div className="form-group">
                                                <input
                                                    type="text"
                                                    name="clientPhone"
                                                    placeholder="Phone number"
                                                    className="form-control"
                                                    id="clientPhone"
                                                    required
                                                />
                                            </div>
                                        </div>
                                        <div className="col-lg-6 col-sm-6">
                                            <div className="col-12">
                                                <select
                                                    className="form-select bg-light border-0"
                                                    name="clientService"
                                                    style={{ height: "55px", color: "black" }}
                                                    required
                                                >
                                                    <option value="">Select A Service</option>
                                                    {services?.length === 0 ? (
                                                        <option value="" disabled>Loading...</option>
                                                    ) : (
                                                        services.map((service) => (
                                                            <option key={service.serviceId} value={service.serviceId}>
                                                                {service.serviceTitle}
                                                            </option>
                                                        ))
                                                    )}
                                                    {!services && (
                                                        <option value="" disabled>Loading...</option>
                                                    )}
                                                </select>
                                            </div>
                                        </div>
                                        <div className="form-group">
                                            <button
                                                type="submit"
                                                className="default-btn btn-two"
                                            >
                                                Book
                                            </button>
                                        </div>
                                    </div>
                                </form>
                            </div>
                        </div>
                    </div>

                    <div className="col-lg-6 col-md-12">
                        <div className="contact-img">
                            <img src="/images/contact-img.png" alt="Image" />
                        </div>
                    </div>
                </div>
            </div>
        </div>
    );
};

结论

完全可以仅通过Server Actions完成需求,无需前端额外处理状态。Server Actions在服务端执行,能够安全地获取服务列表、匹配选中的服务信息,并构造符合要求的提交数据,同时避免前端暴露敏感映射逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 22:24:56