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

React Context列表修改无更新问题求助:删除异常、搜索后恢复

解决数组CRUD列表删除异常问题

问题现象

实现基于数组的简单CRUD列表时,删除元素总是移除最后一项,不过在搜索栏输入内容后列表会恢复正常。已经尝试用扩展运算符避免直接修改原数组,但问题依旧。

问题根源

  1. 使用数组索引作为React组件key
    React依赖key属性识别组件唯一性。当数组元素被删除时,索引会重新排列,导致React错误复用组件实例,显示内容与实际数组数据不匹配。

  2. Card组件本地状态未同步上下文更新
    Card组件将传入的props.name存在本地state中,上下文的items数组更新后,本地state不会自动同步,只有搜索触发App重新渲染时才会更新显示。

  3. editItem函数逻辑错误
    上下文的editItem参数顺序颠倒,且map逻辑错误,会把未匹配的项也修改成旧值,导致编辑功能异常。

修复步骤

1. 替换组件key为唯一标识

将App.tsx中渲染Card时的key={index}改为key={name}(如果后续有重复名称,建议改用带唯一ID的对象数组,比如{id: string, name: string})。

2. 同步Card组件状态与props

移除Card组件中多余的本地name state,直接使用props.name,并通过useEffect监听props变化更新编辑输入框的值。

3. 修正editItem函数逻辑

调整参数顺序,修正map替换逻辑,确保只有匹配项才会被修改。

修正后的完整代码

App.tsx

import './App.css'
import { ItemsContext } from './context/Items';
import { useState, useContext } from 'react';
import { FaSearch } from "react-icons/fa";
import { IoIosAddCircle } from "react-icons/io";
import Card from "./components/Card";

function App() {
    const [search, setSearch] = useState<string>("")
    const [addInput, setAddInput] = useState<string>("")
    const context = useContext(ItemsContext)

    const addItems = () => {
        context?.addItem(addInput)
        setAddInput("")
    }

  return (
    <main>
        <nav>
          <input 
            id='searchInput'
            type="text"
            value={search}
            onChange={e => setSearch(e.target.value)}
          />
          <FaSearch className="inputButton" />
        </nav>  
        <section id='cardBox'>
          {context && context.items?.map((name: string) => {
            if (name.toLowerCase().includes(search.toLowerCase()) || search === ""){
              return <Card key={name} name={name}/>
            }
            return null
          })}
        </section>
        <div id='inputFooter'>
          <input 
            id='addInput'
            type="text"
            value={addInput}
            onChange={e => setAddInput(e.target.value)}
          />
          <IoIosAddCircle className="inputButton" onClick={addItems}/>
        </div>
    </main>
  )
}

export default App;

Items.tsx

import { createContext, ReactNode, useState } from "react";

interface ProviderType{
    items: string[],
    addItem: (name:string) => void
    removeItem: (name:string) => void
    editItem: (prevName:string, newName:string) => void
}

interface ChildrenType{
    children: ReactNode
}

export const ItemsContext = createContext<ProviderType | undefined>(undefined)

export const ItemsProvider = ({ children }: ChildrenType) => {
    const [items,setItems] = useState<string[]>(["Henrique","Iris"])

    const addItem = (name: string) => {
        setItems([...items, name])
    }

    const removeItem = (name: string) => {
        const newArray = items.filter(item => item !== name)
        setItems(newArray)
    }

    const editItem = (prevName: string, newName: string) => {
        const newArray = items.map(item => item === prevName ? newName : item)
        setItems(newArray)
    }

    return(
        <ItemsContext.Provider value={{items, addItem, removeItem, editItem}}>
            {children}
        </ItemsContext.Provider>
    )
}

Card.tsx

import { FaEdit, FaTrash, FaCheck } from "react-icons/fa";
import { useContext, useState, useEffect } from "react";
import { ItemsContext } from "../context/Items";

interface Props{
    name: string
}

function Card(props: Props) {
    const [toogleEdit, setToogleEdit] = useState<boolean>(false)
    const [editInput, setEditInput] = useState<string>(props.name)
    const context = useContext(ItemsContext)

    // 监听props变化,同步编辑输入框的值
    useEffect(() => {
        setEditInput(props.name)
    }, [props.name])

    const changeEdit = () => {
        if(!toogleEdit){
            setToogleEdit(true)
            setEditInput(props.name)
        }else{
            setToogleEdit(false)
            context?.editItem(props.name, editInput)
        }
    }

    return ( 
        <div className="card">
            {!toogleEdit && props.name}
            {toogleEdit && <input 
                type='text'
                value={editInput}
                onChange={e => setEditInput(e.target.value)}
            />}
            <section className="cardButton">
                {!toogleEdit && <FaEdit onClick={changeEdit}/>}
                {toogleEdit && <FaCheck onClick={changeEdit}/>}
                <FaTrash onClick={() => context?.removeItem(props.name)}/>
            </section>
        </div>
    );
}

export default Card;

内容的提问来源于stack exchange,提问作者Henrique Bandeira

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 17:37:33