React动态输入框onChange收集数组对象的正确实现方法
解决React动态输入框值收集异常问题
问题根源
你的核心问题在于没有将动态输入框与对应的状态索引绑定,且状态更新方式错误:
- 直接修改
newTodo.push():React无法检测到原数组的修改,且每次输入单个字符都会触发push,导致数组记录输入过程而非最终值。 setNewTodo([event.target.value]):每次都用新值替换整个数组,最终只会保留最后一个输入框的内容。setNewTodo([...newTodo, event.target.value]):受闭包影响,每次onChange捕获的都是旧的newTodo状态,导致重复叠加旧数组内容。
正确实现方案
不需要单独维护newTodo状态,直接用todoInput数组管理每个输入框的值,提交时再提取有效内容:
- 移除冗余状态:删除单独的
newTodo状态变量。 - 绑定索引更新状态:修改动态输入框的
onChange,根据索引更新todoInput对应位置的值(使用函数式更新避免闭包问题)。 - 提交时提取有效值:从
todoInput中过滤空值,生成最终的待办数组传给Firebase。
修改后的完整代码
const AddTask = () => { const db = getFirestore(); const colTaskRef = collection(db, "Task"); const [newTitle, setNewTitle] = useState(""); const [newInCharge, setNewInCharge] = useState(""); const [newCollabs, setNewCollabs] = useState([]); const [newPriority, setNewPriority] = useState(""); // 直接用todoInput维护每个输入框的值 const [todoInput, setTodoInput] = useState([{ todo: "" }]); const handleTodoAdd = () => { setTodoInput(prev => [...prev, { todo: "" }]); }; const handleTodoRemove = (index) => { setTodoInput(prev => prev.filter((_, i) => i !== index)); }; const handleSelect = (e) => { setNewInCharge(e.target.value); }; const handleCheck = (e) => { setNewCollabs(prev => [...prev, e.target.value]); }; const handleClick = () => { createTask(); }; const createTask = async () => { // 提取非空的待办项 const finalTodos = todoInput .map(item => item.todo.trim()) .filter(todo => todo !== ""); await addDoc(colTaskRef, { Title: newTitle, InCharge: newInCharge, Priority: newPriority, Todos: finalTodos, Collaborators: newCollabs, InProgress: false, Completed: false, }); }; return ( <div className="container add__task-container"> <input className="title-input" type="text" placeholder="Task Title" onChange={(event) => setNewTitle(event.target.value)} /> <label htmlFor="selectInCharge"> Who's in charge of this task?</label> {<GetCollaborators handleSelect={handleSelect} />} <p className="collaborators__checkbox-title"> Who do you want to collaborate with? </p> {<GetCollaboratorsCheckBox handleCheck={handleCheck} />} <label className="priority-label" htmlFor="priority"> Priority? </label> <select name="priority" className="priority-selector" onChange={(event) => setNewPriority(event.target.value)} > <option value="top"> Top </option> <option value="first">First</option> <option value="second">Second</option> <option value="last">Bottom</option> </select> <p className="todos__add-title">To Do list:</p> {todoInput.map((singleTodo, index) => ( <div key={index}> <input type="text" className="todo-input" value={singleTodo.todo} // 绑定value,成为受控组件 onChange={(event) => { // 函数式更新,确保拿到最新的状态 setTodoInput(prev => { const updatedList = [...prev]; updatedList[index].todo = event.target.value; return updatedList; }); }} /> {todoInput.length - 1 === index && todoInput.length < 5 && ( <button className="plus-btn" onClick={handleTodoAdd}> + </button> )} {todoInput.length > 1 && ( <button className="minus-btn" onClick={() => handleTodoRemove(index)} > - </button> )} </div> ))} <button className="btn btn__task-submit" onClick={handleClick}> Submit Task </button> </div> ); };
关键修改点说明
- 受控组件绑定:给输入框加上
value={singleTodo.todo},确保输入框内容与状态同步。 - 函数式状态更新:
setTodoInput(prev => ...)避免闭包导致的旧状态问题,确保每次更新都基于最新的数组。 - 提交时过滤空值:避免Firebase存入空字符串的待办项。
内容的提问来源于stack exchange,提问作者Jonathan Ribak
相关产品推荐
相关产品推荐

