React删除答案按钮异常:点击后误删最后两个答案的问题排查
问题根源与修复方案
核心问题分析
Strict Mode 触发重复执行
React严格模式在开发环境中会重复调用setState相关逻辑,你的handleDeleteAnswer原本每次执行都会pop()一个选项,两次执行就会删掉两个答案。这是开发环境的检测特性,生产环境不会出现,但逻辑本身存在缺陷。数据结构不匹配
初始化问题时,choices是字符串数组,但渲染输入框时用了value={choice.text}——字符串没有text属性,导致输入框始终为undefined,触发不必要的重渲染,加重问题。删除按钮逻辑与需求不符
你期望点击对应答案旁的按钮删除该答案,但当前删除按钮放在所有答案外侧,且逻辑是删除当前问题的最后一个答案,完全不符合需求。
分步修复
1. 统一数据结构,将choices改为对象数组
初始化问题时,把choices改成包含text字段的对象数组,匹配渲染时的取值逻辑:
// 在handleMultipleAnswer和handleSingleAnswer中替换初始化代码 choices: Array(numAnswers).fill(null).map(() => ({ text: "" })),
2. 调整删除按钮位置,传递对应选项索引
把删除按钮放到每个选项的容器内,同时传递该选项的索引给删除函数:
{questionObj.choices.map((choice, choiceIndex) => ( <div key={choiceIndex} style={{ display: "flex", gap: "8px", margin: "4px 0" }}> <input type="text" placeholder={`Choice ${choiceIndex + 1}`} value={choice.text} onChange={(e) => handleAnswerChange(questionIndex, choiceIndex, e.target.value) } /> {/* 每个选项旁添加删除按钮 */} <button onClick={() => handleDeleteAnswer(questionIndex, choiceIndex)}> - </button> </div> ))}
3. 重写删除逻辑,精准删除指定选项
修改handleDeleteAnswer,通过splice删除对应索引的选项,避免操作原数组:
const handleDeleteAnswer = (questionIndex, choiceIndex) => { setPoll((prevPoll) => { const updatedQuestions = [...prevPoll.questions]; // 深拷贝当前问题的选项数组 const updatedChoices = [...updatedQuestions[questionIndex].choices]; updatedChoices.splice(choiceIndex, 1); // 更新问题的选项列表 updatedQuestions[questionIndex] = { ...updatedQuestions[questionIndex], choices: updatedChoices }; return { ...prevPoll, questions: updatedQuestions, }; }); };
4. 修正输入框更新逻辑(可选)
为避免直接修改状态对象,更新handleAnswerChange为纯函数式修改:
const handleAnswerChange = (questionIndex, choiceIndex, newText) => { setPoll((prevPoll) => { const updatedQuestions = [...prevPoll.questions]; updatedQuestions[questionIndex] = { ...updatedQuestions[questionIndex], choices: updatedQuestions[questionIndex].choices.map((choice, idx) => idx === choiceIndex ? { ...choice, text: newText } : choice ) }; return { ...prevPoll, questions: updatedQuestions, }; }); };
完整修复后的组件代码
import React, { useState } from "react"; function NewPoll() { const [poll, setPoll] = useState({ name: "", description: "", multiChoice: false, questions: [], answers: [], }); const handleInputChange = (e) => { const { name, value, type, checked } = e.target; const newValue = type === "checkbox" ? checked : value; setPoll((prevPoll) => ({ ...prevPoll, [name]: newValue, })); }; const handleMultipleAnswer = () => { const question = prompt("Enter the multiple-choice question:"); if (question !== null) { const numAnswers = parseInt( prompt("Enter the number of possible answers:"), 10 ); if (!isNaN(numAnswers)) { const newQuestion = { question, choices: Array(numAnswers).fill(null).map(() => ({ text: "" })), multiChoice: true, }; setPoll((prevPoll) => ({ ...prevPoll, questions: [...prevPoll.questions, newQuestion], })); } } }; const handleSingleAnswer = () => { const question = prompt("Enter the single choice question:"); if (question !== null) { const numAnswers = parseInt( prompt("Enter the number of possible answers:"), 10 ); if (!isNaN(numAnswers)) { const newQuestion = { question, choices: Array(numAnswers).fill(null).map(() => ({ text: "" })), multiChoice: false, }; setPoll((prevPoll) => ({ ...prevPoll, questions: [...prevPoll.questions, newQuestion], })); } } }; const handleAnswerChange = (questionIndex, choiceIndex, newText) => { setPoll((prevPoll) => { const updatedQuestions = [...prevPoll.questions]; updatedQuestions[questionIndex] = { ...updatedQuestions[questionIndex], choices: updatedQuestions[questionIndex].choices.map((choice, idx) => idx === choiceIndex ? { ...choice, text: newText } : choice ) }; return { ...prevPoll, questions: updatedQuestions, }; }); }; const handleDeleteQuestion = (questionIndex) => { setPoll((prevPoll) => { const updatedQuestions = [...prevPoll.questions]; updatedQuestions.splice(questionIndex, 1); return { ...prevPoll, questions: updatedQuestions, }; }); }; const handleDeleteAnswer = (questionIndex, choiceIndex) => { setPoll((prevPoll) => { const updatedQuestions = [...prevPoll.questions]; const updatedChoices = [...updatedQuestions[questionIndex].choices]; updatedChoices.splice(choiceIndex, 1); updatedQuestions[questionIndex] = { ...updatedQuestions[questionIndex], choices: updatedChoices }; return { ...prevPoll, questions: updatedQuestions, }; }); }; return ( <> <h1>Enter poll name:</h1> <br /> <input type="text" name="name" value={poll.name} onChange={handleInputChange} /> <h1>Enter a description:</h1> <textarea rows="10" cols="50" name="description" value={poll.description} onChange={handleInputChange} ></textarea>{" "} <br /> <button onClick={handleMultipleAnswer}> Create a question with multiple answers </button> <button onClick={handleSingleAnswer}> Create a question with a single answer </button> {poll.questions.map((questionObj, questionIndex) => ( <div key={questionIndex} style={{ margin: "16px 0" }}> <h2> {questionObj.question + (questionObj.multiChoice ? " - (multiple choice)" : " - (single choice)")} <br /> <button onClick={() => handleDeleteQuestion(questionIndex)}> Delete question </button> </h2> {questionObj.choices.map((choice, choiceIndex) => ( <div key={choiceIndex} style={{ display: "flex", gap: "8px", margin: "4px 0" }}> <input type="text" placeholder={`Choice ${choiceIndex + 1}`} value={choice.text} onChange={(e) => handleAnswerChange(questionIndex, choiceIndex, e.target.value) } /> <button onClick={() => handleDeleteAnswer(questionIndex, choiceIndex)}> - </button> </div> ))} </div> ))} <br /> <button> Create a new poll </button> </> ); } export default NewPoll;
内容的提问来源于stack exchange,提问作者Iyexis
相关产品推荐
相关产品推荐

