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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 14:36:29