使用Formik+NextUI v2表单时出现受控/非受控输入切换警告求助
使用Formik、Yup和NextUI v2的Input组件开发结账表单时,在任一输入框输入内容,控制台弹出如下警告:
A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component.
相关代码
FormContainer.jsx
import { useFormik } from "formik"; import * as Yup from "yup"; export const CheckoutContainer = () => { const { cart, getTotalPrice, clearCart } = useContext(CartContext); const totalPrice = getTotalPrice(); const phoneRegEx = /^((\+[1-9]{1,4}[ \-]*)|(\([0-9]{2,3}\)[ \-]*)|([0-9]{2,4})[ \-]*)*?[0-9]{3,4}?[ \-]*[0-9]{3,4}?$/; const [orderId, setOrderId] = useState(null); const [loaderIsVisible, setLoaderIsVisible] = useState(false); const { handleSubmit, handleChange, errors } = useFormik({ initialValues: { firstName: "", lastName: "", email: "", address: "", phoneNumber: "", }, onSubmit: (data) => { let order = { buyer: data, items: cart, total: totalPrice, }; setLoaderIsVisible(true); clearCart(); }, validateOnChange: false, validationSchema: Yup.object({ firstName: Yup.string() .required("Please enter a First Name") .min(3) .max(15), lastName: Yup.string() .required("Please enter a Last Name") .min(3) .max(20), email: Yup.string() .email() .required("Please enter a valid email address"), address: Yup.string().required("Please enter an address").min(5), phoneNumber: Yup.string() .required("Please enter a valid phone number") .matches(phoneRegEx, "Phone number is not valid"), }), }); return ( <> {orderId ? ( <div className="h-screen mt-32 px-10 flex flex-col items-center space-y-5"> <h1 className="text-xl"> Your purchase has been successful. Your recipt number is: </h1> <span className="text-2xl font-bold">{orderId}</span> <Link to="/" className="md:w-[50%] w-full h-10 bg-blue-600 rounded-lg text-white hover:bg-blue-500 flex justify-center items-center" > Continue shopping </Link> </div> ) : ( <Checkout totalPrice={totalPrice} handleSubmit={handleSubmit} handleChange={handleChange} errors={errors} loaderIsVisible={loaderIsVisible} /> )} </> ); };
Checkout.jsx
import { Input, Spinner } from "@nextui-org/react"; export const Checkout = ({ totalPrice, handleSubmit, handleChange, loaderIsVisible, errors, }) => { return ( <div className="h-screen mt-28 flex flex-col"> <div className="flex flex-col md:flex-row md:justify-between w-full border-b pb-2 sticky top-20 z-[8] px-10 py-2 bg-white/30 backdrop-blur-sm"> <h1 className="text-2xl font-medium">Checkout</h1> <span className="text-blue-500 md:self-end"> Order summary: {totalPrice} USD </span> </div> <span className="py-10 font-semibold text-4xl px-10"> Now fill out your information. </span> {/* FORM */} <form className="flex flex-col md:w-[50%] space-y-10 z-0 px-10"> <Input label="First Name" variant="bordered" name="firstName" color="primary" errorMessage={errors.firstName} onChange={handleChange} /> <Input label="Last Name" name="lastName" variant="bordered" color="primary" errorMessage={errors.lastName} onChange={handleChange} /> <Input bordered label="Email" variant="bordered" name="email" type="email" color="primary" errorMessage={errors.email} onChange={handleChange} /> <Input label="Street Address" variant="bordered" name="address" color="primary" errorMessage={errors.address} onChange={handleChange} /> <Input label="Phone Number" variant="bordered" name="phoneNumber" color="primary" errorMessage={errors.phoneNumber} onChange={handleChange} /> </form> <button type="submit" onClick={handleSubmit} className="h-10 bg-blue-600 rounded-lg text-white hover:bg-blue-500 md:w-[44%] my-10 mx-10" > <span className=""> Buy</span> {loaderIsVisible && <Spinner className="float-right mr-5 mt-1" />} </button> </div> ); };
这个警告的核心原因是NextUI Input组件未绑定Formik的value属性,组件初始状态为不受控(value默认是undefined),输入内容后Formik更新值,组件从不受控转为受控,触发警告。
修复步骤
1. 传递Formik的values到Checkout组件
修改FormContainer.jsx,从Formik解构出values,并传递给Checkout组件:
// 解构时加入values const { handleSubmit, handleChange, errors, values } = useFormik({ // 原有代码不变 }); // 渲染Checkout时传入values <Checkout totalPrice={totalPrice} handleSubmit={handleSubmit} handleChange={handleChange} errors={errors} loaderIsVisible={loaderIsVisible} values={values} {/* 新增 */} />
2. 给Input组件绑定value属性
修改Checkout.jsx,先接收values props,再给每个Input添加value绑定:
// 接收values props export const Checkout = ({ totalPrice, handleSubmit, handleChange, loaderIsVisible, errors, values, {/* 新增 */} }) => {
然后给每个Input组件添加对应字段的value:
<Input label="First Name" variant="bordered" name="firstName" color="primary" errorMessage={errors.firstName} onChange={handleChange} value={values.firstName} {/* 新增 */} /> <Input label="Last Name" name="lastName" variant="bordered" color="primary" errorMessage={errors.lastName} onChange={handleChange} value={values.lastName} {/* 新增 */} /> <Input bordered label="Email" variant="bordered" name="email" type="email" color="primary" errorMessage={errors.email} onChange={handleChange} value={values.email} {/* 新增 */} /> <Input label="Street Address" variant="bordered" name="address" color="primary" errorMessage={errors.address} onChange={handleChange} value={values.address} {/* 新增 */} /> <Input label="Phone Number" variant="bordered" name="phoneNumber" color="primary" errorMessage={errors.phoneNumber} onChange={handleChange} value={values.phoneNumber} {/* 新增 */} />
3. 优化表单提交逻辑(可选但推荐)
当前提交按钮不在<form>标签内,建议将按钮移入form,利用原生表单提交行为,移除按钮上的onClick:
<form className="flex flex-col md:w-[50%] space-y-10 z-0 px-10" onSubmit={handleSubmit}> {/* 所有Input组件 */} <button type="submit" className="h-10 bg-blue-600 rounded-lg text-white hover:bg-blue-500 md:w-[44%] my-10" > <span className=""> Buy</span> {loaderIsVisible && <Spinner className="float-right mr-5 mt-1" />} </button> </form>
完成以上修改后,表单将完全成为受控组件,控制台警告会消失,同时表单逻辑更符合规范。
内容的提问来源于stack exchange,提问作者Andrés Marino

