React Formik select组件无法渲染循环生成的option选项问题
注册表单出生日期年份选择器渲染异常问题
功能实现背景
开发注册表单的出生日期填写模块,要求校验用户年龄不低于18岁,通过循环生成的方式构造年份下拉选项,用到三个基础逻辑:
- 年龄限制年份计算函数:根据传入的年龄限制,返回对应截止年份
export const AgeLimit = (limit: number) => { let ageLimit = new Date(); ageLimit.setFullYear(ageLimit.getFullYear() - limit); return ageLimit.getFullYear(); }
- 年份区间生成函数:循环生成指定起止区间的年份选项对象,推入目标数组
export const YearLoop = (startValue: number, endValue: number, array: any[]) => { for(let i = startValue; i >= endValue; i--) { let value = {values: `${i}`}; array.push(value) } }
- 可选年份数组生成:组合调用上述两个函数,生成18岁到100岁对应的可选年份范围
export const allYears: any[] = []; YearLoop(AgeLimit(18), AgeLimit(100), allYears);
单独测试allYears的返回数据符合预期,但结合Formik渲染下拉选项时出现异常。
异常表现
封装的Formik自定义SelectField组件代码如下:
import { ReactElement } from 'react' import { ErrorMessage, Field } from "formik" export const SelectField = (props: selectFieldProps) => ( <section className={props.className}> <label htmlFor={props.field}>{props.displayName}</label> <Field as="select" name={props.field} id={props.field} className={props.fieldClassName} > <option value={props.optionValue}>{props.optionLabel}</option> {props.children} </Field> <ErrorMessage name={props.field}>{msg => <p>{msg}</p>}</ErrorMessage> </section> ) interface selectFieldProps { field: string; displayName?: string; className?: string; fieldClassName?: string; children: ReactElement; optionValue: string; optionLabel: string; }
使用该组件渲染年份选择器的代码:
<SelectField field="year" optionValue="" optionLabel="Year"> <> {allYears.map((year, index) => <option key={index} value={year.value}>{year.value}</option>)} </> </SelectField>
经测试两个对照场景均正常运行:
- 传入非循环生成的静态数组时,组件可正常渲染选项
- 直接使用原生select标签搭配
allYears数组渲染时正常工作,原生实现代码:
<select> <option value="">Year</option> {allYears.map(year => <option key={year.values} value={year.values}>{year.values}</option>)} </select>
仅在封装的SelectField组件中传入循环生成的allYears数组时,option选项无法正常渲染。
问题根因
异常由两处代码错误共同导致,和Formik框架、循环生成数组的逻辑无关:
- 对象属性名拼写不一致:
YearLoop生成的年份对象属性名为values(复数形式),但在SelectField中渲染option时读取的是year.value(单数形式),最终生成的option标签value属性和显示文本都是undefined。原生select实现中读取的是year.values,因此可以正常渲染。 - 子节点包裹方式与类型定义不兼容:调用
SelectField时,用空Fragment<></>将option数组包裹为单个React元素,同时组件Props接口中把children类型限制为单个ReactElement。Formik的Field组件在as="select"场景下,只会直接渲染第一层子节点,不会递归展开Fragment内部的option列表,直接导致选项无法显示。此外使用数组index作为option的key,也容易触发React节点复用异常。
修复方案
按以下步骤修改即可解决问题:
- 统一属性命名:将
YearLoop中的属性名从values改为value,和option原生属性名保持一致,避免后续出现拼写混淆:
export const YearLoop = (startValue: number, endValue: number, array: any[]) => { for(let i = startValue; i >= endValue; i--) { let value = {value: `${i}`}; array.push(value) } }
- 修正
SelectField的children类型定义,支持传入元素数组:
interface selectFieldProps { field: string; displayName?: string; className?: string; fieldClassName?: string; children: ReactElement | ReactElement[]; optionValue: string; optionLabel: string; }
- 去掉调用
SelectField时包裹option的多余Fragment,使用年份值作为option的key(替换原有的index作为key的写法):
<SelectField field="year" optionValue="" optionLabel="Year"> {allYears.map((year) => <option key={year.value} value={year.value}>{year.value}</option>)} </SelectField>
内容的提问来源于stack exchange,提问作者Joro
相关产品推荐
相关产品推荐

