为多输入项的useState设置唯一Key问题求解
问题解决:动态新增输入框同步变化的问题
你的推测有部分道理,但核心问题有两个:组件key不唯一,以及训练次数的输入框没有设置独立的name属性,导致所有组共用同一个Formik状态字段,修改一个就会同步所有。下面是具体的修复步骤:
1. 修复组件Key问题
你当前用key={n},但[...Array(numberOfMusclesInProgram)]生成的数组元素全是undefined,所以所有组件的key都是重复的,React无法区分不同的组件实例。改成用map的索引i作为key:
{[...Array(numberOfMusclesInProgram)].map((n, i) => ( <DisplayMuscleInProgram key={i} index={i} /> ))}
同时给DisplayMuscleInProgram组件添加index属性,用来区分不同的组。
2. 给训练次数输入框设置独立的name
现在所有训练次数的Field用的都是name="numberOfExercices",这会导致Formik把所有输入框绑定到同一个状态字段。改成和肌肉类型一样,带上索引:
<Field as="select" className="col-span-2 rounded-lg bg-lightGray p-3" name={`numberOfExercices-${props.index}`} > {numberOfExercices(10)} </Field>
3. 调整初始值结构(可选但更规范)
原来的initialValues是单个字段,现在对应多组输入,应该初始化所有需要的字段;或者直接改用数组结构管理多组数据,后续维护更方便:
// 改用数组管理多组数据 const [muscleGroups, setMuscleGroups] = useState([{ muscle: getAllMuscles()[0], numberOfExercices: 0 }]); // 添加按钮逻辑改成 <button onClick={() => setMuscleGroups([...muscleGroups, { muscle: getAllMuscles()[0], numberOfExercices: 0 }])} type="button" className="rounded-md border" > Add </button> // 渲染时遍历数组 {muscleGroups.map((group, i) => ( <DisplayMuscleInProgram key={i} index={i} /> ))}
完整修改后的代码
import { Formik, Form, Field } from "formik"; import React, { useState } from "react"; import { useNavigate } from "react-router-dom"; import { getAllMuscles, numberOfExercices } from "../helpers/dataModuler"; import { setInLocalStorage } from "../helpers/localStorageHandler"; export const ProgramForm: React.FC<{}> = () => { // 用数组管理多组训练数据,结构更清晰 const [muscleGroups, setMuscleGroups] = useState([{ muscle: getAllMuscles()[0], numberOfExercices: 0 }]); const navigate = useNavigate(); const handleSubmit = (values: any) => { setInLocalStorage("program", values); navigate("/programme"); }; // 根据现有组数生成Formik初始值 const getInitialValues = () => { const initialValues: any = {}; muscleGroups.forEach((_, i) => { initialValues[`muscle-${i}`] = getAllMuscles()[0]; initialValues[`numberOfExercices-${i}`] = 0; }); return initialValues; }; return ( <> <Formik initialValues={getInitialValues()} onSubmit={(values) => handleSubmit(values)} > <Form> <div id="container"> {muscleGroups.map((_, i) => ( <DisplayMuscleInProgram key={i} index={i} /> ))} </div> <button onClick={() => setMuscleGroups([...muscleGroups, { muscle: getAllMuscles()[0], numberOfExercices: 0 }])} type="button" className="rounded-md border" > Add </button> <div className="text-center"> <button type="submit" className="mt-8 rounded-lg bg-primary p-2 text-white" > Let's gooooo 💪 </button> </div> </Form> </Formik> </> ); }; export const DisplayMuscleInProgram = (props: { index: number }) => { const listOfMuscles = getAllMuscles(); return ( <> <div className="my-4 grid grid-cols-5 justify-between gap-5"> <Field as="select" className="col-span-3 rounded-lg bg-lightGray p-3" name={`muscle-${props.index}`} > {listOfMuscles.map((muscle, key) => ( <option key={key} value={muscle}>{muscle}</option> ))} </Field> <Field as="select" className="col-span-2 rounded-lg bg-lightGray p-3" name={`numberOfExercices-${props.index}`} > {numberOfExercices(10)} </Field> </div> </> ); };
关键改动点总结
- 替换了重复的key,用索引保证每个组件实例唯一
- 给每个训练次数输入框设置了带索引的独立name,避免状态冲突
- 改用数组管理多组数据,让状态结构更清晰,后续扩展更方便
内容的提问来源于stack exchange,提问作者Johan
相关产品推荐
相关产品推荐

