基于Formik和React的可复用自定义Input组件报错不显示问题排查
自定义Formik Input组件错误信息不显示问题排查
问题描述
我开发了一个可通过props接收数据的自定义基础表单Input组件,集成Formik使用时遇到错误信息无法显示的问题,请帮忙排查代码实现中的错误并解决。
原组件代码
/* eslint-disable react/prop-types */ const Input = (props) => { const {formik, icon, title,type,otherState,btn1,btn2, name} = props Input.defaultProps = { type: "text", otherState: null, btn1: null, btn2: null, name: title.toLowerCase() } if (otherState) { var {state, setState} = otherState } if (btn1) { var {icon1, text1, action1} = btn1 } if (btn2) { var {icon2, text2, action2} = btn2 } return ( <div className="mb-2"> <span className="flex"> <img className="w-6 h-6 mr-2" src={icon} alt={title} /> <label htmlFor={name} className="block text-sm font-semibold text-textPrimary dark:text-dark_textPrimary" > {title} </label> </span> <span className="flex w-full"> {console.log(formik.touched.name)} <input type={type} name={name} value={formik.values.name} title={`${formik.touched.name && formik.values.name == "" ? `${title} is required` : ""}`} onChange={formik.handleChange} onBlur={formik.handleBlur} placeholder={ formik.touched.name && formik.errors.name ? `${title} is required` : `${title}` } className={`block w-full px-4 py-2 mt-2 text-textPrimary dark:text-dark_textPrimary bg-background dark:bg-dark_background border-2 rounded-md focus:ring-secondary ${formik.touched.name && formik.errors.name ? " border-error_red placeholder-error_red" : "border-cardBorder dark:border-dark_cardBorder"}`} /> { btn1 || btn2 ? ( <img className="w-6 h-7 cursor-pointer mt-3 transition duration-500 ease-in-out transform hover:-translate-y-1 hover:scale-110" onClick={() => action1()} src={state ? icon1 : icon2} title={!state ? text1 : text2} />) : null } </span> { formik.touched.name && formik.errors.name && formik.values.name != "" ? ( <h3 className="text-xs text-error_red">{formik.errors.name}</h3> ) : null } </div> ) } export default Input
组件调用示例
<Input formik={formik} icon={emailIcon} title="Email" name="email" type="email" /> <Input formik={formik} icon={passwordIcon} name="password" title="Password" type={showPassword ? "password" : "text"} otherState={{'state':showPassword, 'setState':setShowPassword}} btn1={{'icon1':eyeOpenIcon, 'text1':"Hide Password", 'action1':() => setShowPassword(!showPassword)}} btn2={{'icon2':eyeCloseIcon, 'text2':"Show Password", 'action2':() => setShowPassword(!showPassword)}} />
错误排查与修复方案
核心错误点
- Formik字段属性访问错误:所有
formik.touched.name、formik.values.name、formik.errors.name的写法都是错误的,这里的name是props传入的变量(比如"email"),必须用方括号语法访问动态属性,即formik.touched[name],否则会固定访问formik.touched.name这个不存在的属性。 - defaultProps定义位置错误:将
Input.defaultProps放在组件函数内部,会导致每次组件渲染时都重新赋值,且不符合React规范,应该移到组件外部。 - 错误信息显示条件逻辑错误:原条件
formik.values.name != ""会排除空值必填错误的显示场景,应该移除该条件,只要字段被触碰且有错误就显示。 - 变量声明不规范:使用
var声明变量存在变量提升问题,建议改用let/const。 - 按钮交互逻辑漏洞:仅调用
action1,未处理只有btn2的情况,且图片切换逻辑可以优化。
修复后的组件代码
/* eslint-disable react/prop-types */ const Input = (props) => { const { formik, icon, title, type, otherState, btn1, btn2, name } = props const { state, setState } = otherState || {} const { icon1, text1, action1 } = btn1 || {} const { icon2, text2, action2 } = btn2 || {} // 动态获取Formik相关字段状态 const fieldTouched = formik.touched[name] const fieldValue = formik.values[name] const fieldError = formik.errors[name] return ( <div className="mb-2"> <span className="flex"> <img className="w-6 h-6 mr-2" src={icon} alt={title} /> <label htmlFor={name} className="block text-sm font-semibold text-textPrimary dark:text-dark_textPrimary" > {title} </label> </span> <span className="flex w-full"> <input type={type} name={name} value={fieldValue || ''} title={`${fieldTouched && !fieldValue ? `${title} is required` : ""}`} onChange={formik.handleChange} onBlur={formik.handleBlur} placeholder={ fieldTouched && fieldError ? `${title} is required` : `${title}` } className={`block w-full px-4 py-2 mt-2 text-textPrimary dark:text-dark_textPrimary bg-background dark:bg-dark_background border-2 rounded-md focus:ring-secondary ${fieldTouched && fieldError ? " border-error_red placeholder-error_red" : "border-cardBorder dark:border-dark_cardBorder"}`} /> {(btn1 || btn2) && ( <img className="w-6 h-7 cursor-pointer mt-3 transition duration-500 ease-in-out transform hover:-translate-y-1 hover:scale-110" onClick={() => state ? action2?.() : action1?.()} src={state ? icon2 : icon1} title={state ? text2 : text1} /> )} </span> {fieldTouched && fieldError && ( <h3 className="text-xs text-error_red">{fieldError}</h3> )} </div> ) } // 将defaultProps移到组件外部 Input.defaultProps = { type: "text", otherState: null, btn1: null, btn2: null, name: '' } // 处理name的默认值逻辑(原defaultProps无法访问title,所以新增包装组件) const EnhancedInput = (props) => { const { title, name } = props return <Input {...props} name={name || title?.toLowerCase() || ''} /> } export default EnhancedInput
额外说明
- 原
defaultProps中name: title.toLowerCase()的写法不生效,因为defaultProps无法访问其他props,所以新增了一层包装组件EnhancedInput来处理name的默认值逻辑。 - 对按钮交互逻辑做了优化,根据
state的值调用对应的action1或action2,并使用可选链运算符?.避免空值调用报错。 - 统一提取了Formik字段的状态变量,让代码更清晰易维护。
内容的提问来源于stack exchange,提问作者Panda
相关产品推荐
相关产品推荐

