使用Server Action时无法读取Shadcn Form Select选中值的问题
解决Shadcn UI Select组件值无法在Next.js Server Action中读取的问题
问题说明
开发邮件发送表单时,使用Shadcn UI的Select组件、React Hook Form(RHF)和Next.js Server Action实现服务端验证,输入框、文本域等字段的值可正常读取,但Select组件的选中值无法在Server Action的FormData中获取。
原因分析
Shadcn UI的Select组件基于Radix UI构建,默认不会自动生成用于表单提交的<input>元素,而Next.js Server Action通过原生FormData获取字段值时,依赖表单内的标准表单控件(如input、select等)来收集数据。RHF的FormField虽能管理Select的状态,但如果没有对应的表单控件绑定,提交时该字段不会被加入FormData。
解决方案
在FormField内部添加一个隐藏的<input>元素,将其与RHF的field属性关联,让RHF自动同步Select的选中值到这个input,表单提交时该值就会被包含到FormData中。
修改后的表单组件代码
'use client' import * as z from "zod" import React from 'react' import { zodResolver } from "@hookform/resolvers/zod" import { useForm } from "react-hook-form" import { Button } from "@/components/ui/button" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Input } from "@/components/ui/input" import { Textarea } from "@/components/ui/textarea" import {toast} from "react-hot-toast" import { sendEmail} from "@/actions/integrations/Email.actions" import EmailSubmitButton from "./EmailSubmitButton" import { useFormState } from "react-dom" import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select" const formSchema = z.object({ from: z.string().email(), to: z.string().email(), subject: z.string().min(3, { message: "主题不能为空且至少3个字符。", }), message: z.string().min(10, { message: "消息不能为空且至少10个字符。", }), calendlylink : z.string() }) const EmailApplicantForm = () => { const initialState = {message : null, errors : { } }; const [state, dispatch] = useFormState(sendEmail, initialState) const form = useForm<z.infer<typeof formSchema>>({ resolver: zodResolver(formSchema), defaultValues: { from: "test@gmail.com", to: "test123@gmail.com", subject: "", message: "", calendlylink : "" }, }) type InterviewLinkType = { id : string, title : string, link : string } const interviewLinks: InterviewLinkType[] = [ { id : '1', title : '前端工程师第一轮面试', link : 'https://calendly.com/sobitp59/interview-round-1', }, { id : '2', title : '后端工程师第二轮面试', link : 'https://calendly.com/sobitp59/interview-for-backend-engineer-round-2', }, { id : '3', title : '后端工程师第三轮面试', link : 'https://calendly.com/sobitp59/interview-for-backend-engineer-round-3', }, { id : '4', title : '后端工程师第四轮面试', link : 'https://calendly.com/sobitp59/interview-for-backend-engineer-round-4', }, ] return ( <Form {...form}> <form action={dispatch} className="space-y-8"> {/* 其他字段 */} <FormField control={form.control} name="calendlylink" render={({ field }) => ( <FormItem> <FormLabel>选择Calendly链接</FormLabel> {/* 添加隐藏input同步Select值到FormData */} <input type="hidden" {...field} /> <Select onValueChange={field.onChange} value={field.value}> <FormControl> <SelectTrigger> <SelectValue placeholder="选择你的Calendly链接"/> </SelectTrigger> </FormControl> <SelectContent> {interviewLinks.map(({id, title, link} : InterviewLinkType) => ( <SelectItem key={id.toString()} value={link}>{title}</SelectItem> ))} </SelectContent> </Select> <FormMessage /> </FormItem> )} /> <div className="flex justify-end"> <EmailSubmitButton /> </div> </form> </Form> ) } export default EmailApplicantForm
代码说明
- 在
FormField的渲染逻辑中新增隐藏<input>,通过{...field}绑定RHF的字段属性,包括name、value和ref,确保值能同步到表单提交数据中。 - 隐藏input不会影响页面UI,仅用于在表单提交时将Select的选中值以
calendlylink为键名传入FormData。 - 保留原Select组件的交互逻辑,确保用户选择体验不受影响。
验证效果
修改后提交表单,Server Action中通过formData.get('calendlylink')即可获取到Select的选中值,Zod验证也能正常通过。
内容的提问来源于stack exchange,提问作者Sobit Prasad
相关产品推荐
相关产品推荐

