Next.js 14.1.4中Formik结合Server Actions提交表单时参数无法传入的问题求助
Next.js 14.1.4中Formik结合Server Actions提交表单时参数无法传入的问题求助
大家好,我最近在开发Next.js 14.1.4项目时遇到了一个棘手的问题:用户先选择一项服务,页面会根据所选服务动态渲染对应字段的表单,表单用Formik+Yup做验证,提交时需要调用带"use server"指令的Server Action函数向CRM系统创建线索,但这个Server Action始终接收不到我传入的表单参数——明明在客户端的提交函数里能正常打印出values,传到Server Action里就变成空的了。
我的代码结构如下:
首页(IndexPage)
这是一个服务器组件,负责获取服务类型数据并传递给Manager组件:
import Manager from "@/components/manager" import Header from "@/components/header" import { getTypes } from "@/utils/requests" export default async function Home() { const types = await getTypes() return ( <> <Header/> <main> <Manager types={types}/> </main> </> ) }
Manager组件(Manager.jsx)
客户端组件,根据用户是否选择服务,切换显示服务选择器或动态生成的表单:
'use client' import { useContext } from 'react' import DataContext from '@/components/context/data-context' import ServiceChoice from '@/components/organisms/service-choice' import Form from '@/components/form' import InlineSVG from 'react-inlinesvg' import createFieldObject from '@/utils/create-fields-object' import createYupSchema from '@/utils/create-yup-schema' import Link from 'next/link' export default function Manager ({types}) { const { service, setService } = useContext(DataContext) if (service) { const validationSchema = createYupSchema(service?.inputs) const { fields, filesKey } = createFieldObject(service?.inputs) return ( <> <div className="instruction-container flex flex-col"> <div className="flex justify-end"> <button type='button' className='flex flex-row items-center cursor-pointer inherit-a-font' onClick={() => {setService(null)}}> К выбору услуг <InlineSVG src='/icons/arrow-left.svg' width={35} height={25} strokeWidth={2}/> </button> </div> <p className='inherit-a-font'>Мы ценим ваше время и стремимся обеспечить быструю и эффективную обработку заявок. Пожалуйста, заполняйте все поля в форме максимально корректно и полно. Это позволит Вам избежать дополнительных уточнений и сократить время на обработку вашей заявки.</p> <p className='inherit-a-font'>Вы всегда можете задать вопрос по заполнению, связавшись с нами по номеру телефону: <Link href='tel:88005502707'>{"8-(800)-550-27-07"}</Link></p> <p style={{color: 'red'}} className='inherit-a-font'>Поля, помеченные {"\"*\""}, являются обязательными к заполнению</p> </div> <Form inputs={service?.inputs} validationSchema={validationSchema} fields={fields} filesKey={filesKey}/> </> ) } return (<ServiceChoice types={types}/>) }
表单组件(Form.jsx)
客户端组件,用Formik处理表单逻辑,提交时调用submitFunction,再由它调用Server Action:
/* eslint-disable react-hooks/rules-of-hooks */ 'use client' import { useFormik } from 'formik' import InputManager from '@/components/inputs/input-manager' import { motion } from 'framer-motion' import { getOriginalImageUrl } from '@/utils/get-image-url' import Link from 'next/link' import InlineSVG from 'react-inlinesvg' import { useContext, useState } from 'react' import DataContext from '@/components/context/data-context' import CircleLoader from '@/components/atoms/circle-loader' import { createLead } from '@/utils/bx-requests' const submitFunction = async (values, filesKey, serviceName) => { // try { console.log(values, filesKey, serviceName) // 这里能正常打印出用户填写的values const status = await createLead(values) // console.log(status) // } // catch (error) { // console.log(error) // } } export default function Form ({inputs, validationSchema, fields, filesKey}) { if (!Array.isArray(inputs)) return (<></>) const { service } = useContext(DataContext) const [isLoading, setIsLoading] = useState(false) const [isSuccess, setIsSuccess] = useState(false) const formik = useFormik({ initialValues: fields, validationSchema: validationSchema, onSubmit: (values) => {submitFunction(values, filesKey, service.name)} }) return ( <form onSubmit={formik.handleSubmit} className='flex flex-col'> <div className={`inputs-container flex flex-col ${isLoading || isSuccess ? 'disabled' : ''}`}> {inputs.map((item) => ( <div className="input-container flex flex-col" key={item.label}> <label className='flex flex-row items-center flex-wrap' style={{gap: '5px'}}> {item.label}{item.isRequired ? <span style={{fontSize: 'inherit', fontWeight: 'inherit', color: 'red'}}> * </span> : <></>} {item?.file?.data && <Link href={getOriginalImageUrl(item.file)} target='_blank' alt='Ссылка на файл' className='inherit-label-font'>{`(${item.filename || "Ссылка"})`}</Link>} {formik.errors[item.bitrixKey] && formik.touched[item.bitrixKey] && <motion.span initial={{opacity: 0}} animate={{opacity: 1}} className='inherit-p-font' style={{ color: 'red'}}> {formik.errors[item.bitrixKey].toLowerCase()} </motion.span> } </label> <InputManager input={item} formik={formik}/> </div> ))} </div> <button className="flex flex-row items-center" style={{alignSelf: 'flex-end'}} disabled={isSuccess || isLoading} type='submit'> <p className='inherit-input-font'>{isSuccess ? "Успешно" : "Отправить заявку"}</p> {isLoading ? <CircleLoader/> : <InlineSVG src='/icons/upload.svg' width={30} height={20}/>} </button> </form> ) }
Server Action文件(bx-requests.jsx)
这里的createLead函数打印的values始终为空:
"use server" // Lead creating export async function createLead (values) { console.log('in lead creating', values) // 这里的values总是undefined或者空对象 return true // requests logic }
我尝试过的解决方案
我特意创建了一个测试页,提前在服务器端渲染表单(不需要用户选择服务动态切换),但问题依然存在,Server Action还是接收不到参数:
测试页(TESTPAGE)
import Form from "@/components/formTest"; import { getTypes } from "@/utils/requests"; export default async function Page () { const types = await getTypes() const service = types[2].attributes return ( <Form inputs={service.inputs} service={service}/> ) }
测试表单(TESTFORM)
/* eslint-disable react-hooks/rules-of-hooks */ 'use client' import { useFormik } from 'formik' import InputManager from '@/components/inputs/input-manager' import { motion } from 'framer-motion' import { getOriginalImageUrl } from '@/utils/get-image-url' import Link from 'next/link' import InlineSVG from 'react-inlinesvg' import { useContext, useState } from 'react' import DataContext from '@/components/context/data-context' import CircleLoader from '@/components/atoms/circle-loader' import { createLead } from '@/utils/bx-requests' import createFieldObject from '@/utils/create-fields-object' import createYupSchema from '@/utils/create-yup-schema' const submitFunction = async (values, filesKey, serviceName) => { // try { console.log(values, filesKey, serviceName) const status = await createLead(values) // console.log(status) // } // catch (error) { // console.log(error) // } } export default function Form ({inputs, service}) { const validationSchema = createYupSchema(service?.inputs) const { fields, filesKey } = createFieldObject(service?.inputs) if (!Array.isArray(inputs)) return (<></>) const [isLoading, setIsLoading] = useState(false) const [isSuccess, setIsSuccess] = useState(false) const formik = useFormik({ initialValues: fields, validationSchema: validationSchema, onSubmit: (values) => {submitFunction(values, filesKey, service.name)} }) return ( <form onSubmit={formik.handleSubmit} className='flex flex-col'> <div className={`inputs-container flex flex-col ${isLoading || isSuccess ? 'disabled' : ''}`}> {inputs.map((item) => ( <div className="input-container flex flex-col" key={item.label}> <label className='flex flex-row items-center flex-wrap' style={{gap: '5px'}}> {item.label}{item.isRequired ? <span style={{fontSize: 'inherit', fontWeight: 'inherit', color: 'red'}}> * </span> : <></>} {item?.file?.data && <Link href={getOriginalImageUrl(item.file)} target='_blank' alt='Ссылка на файл' className='inherit-label-font'>{`(${item.filename || "Ссылка"})`}</Link>} {formik.errors[item.bitrixKey] && formik.touched[item.bitrixKey] && <motion.span initial={{opacity: 0}} animate={{opacity: 1}} className='inherit-p-font' style={{ color: 'red'}}> {formik.errors[item.bitrixKey].toLowerCase()} </motion.span> } </label> <InputManager input={item} formik={formik}/> </div> ))} </div> <button className="flex flex-row items-center" style={{alignSelf: 'flex-end'}} disabled={isSuccess || isLoading} type='submit'> <p className='inherit-input-font'>{isSuccess ? "Успешно" : "Отправить заявку"}</p> {isLoading ? <CircleLoader/> : <InlineSVG src='/icons/upload.svg' width={30} height={20}/>} </button> </form> ) }
现在我实在找不到问题出在哪了,有没有朋友遇到过类似的情况?或者我的代码里有什么地方写得不对?
备注:内容来源于stack exchange,提问作者user23915831
相关产品推荐
相关产品推荐

