You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ReactJS数组内可编辑可删除对象异常:删除目标项时误删最后一项

问题与解决方案

问题描述

编写的React组件用于实现步骤的添加、编辑与删除功能,但删除指定步骤时总是删除最后一个步骤,希望解决该问题并保持步骤在数组中的位置不变。

问题根源

  1. key使用index导致渲染混乱:React列表渲染时用index作为key,删除元素后index会重新分配,React的diff算法无法正确识别每个步骤组件,导致渲染错误,表现为删除最后一个步骤。
  2. handleNewStep中id赋值的异步问题:setStepsMade是异步更新,直接使用stepsMade会获取到旧状态值,虽暂时不会出现重复id,但存在逻辑隐患。
  3. 文本输入框使用非受控模式:defaultValue仅初始化输入框内容,后续state更新不会同步到输入框,应使用受控组件模式(value+onChange)。

修正后的代码

// imports
import { useState } from 'react';
import { useAuthContext } from '../../hooks/useAuthContext';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPlus, faTrashCan } from '@fortawesome/free-solid-svg-icons';

// styles
import './Create.css';

export default function Create() {
  const { user } = useAuthContext();
  
  const [projectTitle, setProjectTitle] = useState('');
  const [projectDescription, setProjectDescription] = useState('');
  const [projectThumnbnail, setProjectThumbnail] = useState('');
  const [projectKeywords, setProjectKeywords] = useState([]);
  const [steps, setSteps] = useState([{ photoSrc: '', stepDescription: 'hi nice to meet you', stepThumbnail: null, id: 0 }]);

  const handleNewStep = () => {
    setSteps(prevSteps => {
      const newId = prevSteps.length;
      return [...prevSteps, { photoSrc: '', stepDescription: 'hi nice to meet you', stepThumbnail: null, id: newId }];
    });
  }

  const handleRemoveStep = id => {
    setSteps(prevSteps => prevSteps.filter(step => step.id !== id)); 
  }

  const handleUpdateStepThumbnail = (file, id) => {
    setSteps(prevSteps => prevSteps.map(s => { 
      if(s.id === id) {
        return {...s, stepThumbnail: file};
      } else {
        return s;
      }
    }))
  }

  const handleUpdateStepDescription = (value, id) => {
    setSteps(prevSteps => prevSteps.map(s => { 
      if(s.id === id) {
        return {...s, stepDescription: value};
      } else {
        return s;
      }
    }))  
  }

  return (
    <div>
      <label>
        <span>project name:</span>
        <input type='text' value={projectTitle} onChange={e => setProjectTitle(e.target.value)}/>
      </label>

      <label>
        <span>project description:</span>
        <input type='text' value={projectDescription} onChange={e => setProjectDescription(e.target.value)}/>
      </label>

      <label>
        <span>project keywords:</span>
        <input type='text' value={projectKeywords.join(',')} onChange={e => setProjectKeywords(e.target.value.split(','))}/>
      </label>

      <label>
        <span>project picture:</span>
        <input type='file' onChange={e => setProjectThumbnail(e.target.files[0])}/>
      </label>

      {steps.map((step) => (   
      <div className='project-step' key={step.id}>
        <div>
          <FontAwesomeIcon icon={faTrashCan} onClick={() => handleRemoveStep(step.id)}/>
        </div>
        <img src={step.photoSrc}/>
        <input type='file' onChange={e => handleUpdateStepThumbnail(e.target.files[0], step.id)}/>
        <input type='text' value={step.stepDescription} onChange={e => handleUpdateStepDescription(e.target.value, step.id)}/>
      </div>
      ))}

      <div>
        <FontAwesomeIcon icon={faPlus} onClick={handleNewStep}/>
      </div>
    </div>
  )
}

关键修改点说明

  • 替换key为step.id:确保每个步骤组件有唯一且稳定的key,避免删除元素后React渲染混乱。
  • 移除stepsMade状态:直接通过prevSteps.length生成新步骤的id,避免异步状态更新导致的id错误。
  • 文本输入框改为受控模式:使用value绑定state,确保输入框内容与state同步。
  • 修正file input的onChange处理:直接传递e.target.files[0]而非e.target.value,因为file input的value是路径字符串,不是文件对象。

内容的提问来源于stack exchange,提问作者pablo scyzoryk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 10:40:28