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

React隐藏Div渲染异常:模态框始终显示首个列表元素值

React循环渲染子组件时模态框显示异常问题

问题现象

  • 父组件LateralNav通过map循环渲染子组件DeleteClientComponent,点击任意元素的删除按钮打开模态框时,模态框内始终显示列表首个元素的idx值,而非当前点击元素的对应值
  • useEffect、displayDeleteClientModal方法中日志显示值正确,但模态框、deleteClient及cancelModal方法中日志值异常
  • 若初始将模态框设为display:block则值正常,怀疑是渲染相关问题

问题截图

模态框显示异常

原代码示例

子组件 DeleteClientComponent

export default function DeleteClientComponent({getClients, id, idx}: {getClients: () => Promise<void>, id: string, idx: number}) {
    const [isLoading, setIsLoading] = useState(false);
    const [savedID, setID] = useState("");
  
    function displayDeleteClientModal() {        
        console.log("display: ", savedID);
        console.log("display: ", id); // 此处日志值正确
        
        let modal = document.getElementById("my-modal") as HTMLElement;
        
        modal.style.display = 'block'
    }

    const deleteClient = async () => {
        console.log("delete: ", savedID);
        console.log("delete: ", id);
        console.log("delete: ", idx); // 此处日志值异常
    }

    function cancelModal() {
        console.log("cancel: ", savedID);
        console.log("cancel: ", id); // 此处日志值异常
        let modal = document.getElementById("my-modal") as HTMLElement;

        modal.style.display = 'none'
    }

    useEffect(() => {
        console.log(id) // 此处值正确
        setID(id)
    })

    return (
        <div>
            <div className="w-full flex justify-end">
                <button type="button" className='' onClick={displayDeleteClientModal}>X</button>
            </div>

            <p>{idx}</p> {/* 此处显示正确值 */}
            {/* 模态框 */}
            <div id="my-modal"
                className="fixed hidden inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full">
                <div
                className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
                    <div className="text-center">
                        <h3 className="text-lg leading-6 font-medium text-gray-900">确定要删除这个客户吗?</h3>
                        <p className="text-gray-900">{idx}</p> {/* 模态框内始终显示0 */}
                    </div>
                    <div className="items-center px-4 py-3 flex gap-6">

                        <button id="ok-btn" onClick={() => deleteClient()}
                            className="px-4 py-2 bg-green-500 text-white text-base font-medium rounded-md w-full shadow-sm hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-green-300">
                            确认
                        </button>
                        <button id="cancel-btn" onClick={cancelModal}
                            className="px-4 py-2 bg-red-500 text-white text-base font-medium rounded-md w-full shadow-sm hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-300">
                            取消
                        </button>
                    </div>
                </div>
            </div>
        </div>
    )
}

父组件 LateralNav

export default function LateralNav() {
  interface singleClient {
    client: string,
    id: string
  }

  const [clients, setClients] = useState<Array<singleClient>>([]);

  async function getClients() {
    try {
      const response = await fetch('/api/client', {
        method: 'GET',
        headers: {
        'Content-Type': 'application/json'
        }
      });
      setClients(await response.json())
    } catch {
      console.log("error");
    } finally {
    }         
  }

  useEffect(() => {
    getClients()
  }, [])

    return (
        <div>
            {
              clients.length > 0 ? (
                clients.map((client, index) => {
                  return (
                    <div key={index} className='grid gap-4 grid-cols-client'>
                      <Link href={`/client/${encodeURIComponent(client.client)}`}>
                        <div className='mb-0.5 py-0.5 px-2 bg-slate-800 hover:bg-slate-600 hover:font-bold'>
                          <p>{client.client} - {index}</p>
                        </div>
                      </Link>
                      {client.id.length > 0 && (<DeleteClientComponent getClients={getClients} id={client.id} idx={index} />)}
                    </div>
                  )
                })
              ) : (
                <p>暂无数据</p>
              )
            }
        </div>
    )
}

问题根源

  1. 重复DOM ID冲突:每个子组件的模态框都用了相同的id="my-modal",调用document.getElementById时只会返回第一个匹配的DOM元素,导致所有删除按钮操作的都是第一个子组件的模态框
  2. 直接操作DOM违背React设计原则:手动修改DOM样式的方式绕过了React的状态管理,导致UI和状态不同步

修复方案

1. 子组件改用状态控制模态框显隐

移除重复的id,在子组件内部维护isModalOpen状态,通过状态切换模态框的显示隐藏:

export default function DeleteClientComponent({getClients, id, idx}: {getClients: () => Promise<void>, id: string, idx: number}) {
    const [isLoading, setIsLoading] = useState(false);
    const [savedID, setID] = useState("");
    // 新增状态控制模态框显隐
    const [isModalOpen, setIsModalOpen] = useState(false);
  
    function displayDeleteClientModal() {        
        console.log("display: ", id);
        setIsModalOpen(true); // 用状态打开模态框
    }

    const deleteClient = async () => {
        console.log("delete: ", id);
        console.log("delete: ", idx);
        // 执行删除逻辑后关闭模态框
        setIsModalOpen(false);
        await getClients();
    }

    function cancelModal() {
        console.log("cancel: ", id);
        setIsModalOpen(false); // 用状态关闭模态框
    }

    useEffect(() => {
        setID(id)
    }, [id]) // 添加依赖项,避免不必要的重渲染

    return (
        <div>
            <div className="w-full flex justify-end">
                <button type="button" className='' onClick={displayDeleteClientModal}>X</button>
            </div>

            <p>{idx}</p>
            {/* 用状态控制模态框的显示,移除固定id */}
            <div 
                className={`fixed ${isModalOpen ? 'block' : 'hidden'} inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full`}>
                <div
                className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
                    <div className="text-center">
                        <h3 className="text-lg leading-6 font-medium text-gray-900">确定要删除这个客户吗?</h3>
                        <p className="text-gray-900">{idx}</p>
                    </div>
                    <div className="items-center px-4 py-3 flex gap-6">

                        <button onClick={() => deleteClient()}
                            className="px-4 py-2 bg-green-500 text-white text-base font-medium rounded-md w-full shadow-sm hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-green-300">
                            确认
                        </button>
                        <button onClick={cancelModal}
                            className="px-4 py-2 bg-red-500 text-white text-base font-medium rounded-md w-full shadow-sm hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-300">
                            取消
                        </button>
                    </div>
                </div>
            </div>
        </div>
    )
}

2. 优化父组件的map key

避免用index作为map的key(当列表元素顺序变化时可能导致渲染异常),改用client的唯一id:

// 父组件map部分修改
clients.map((client, index) => {
  return (
    <div key={client.id} className='grid gap-4 grid-cols-client'>
      {/* ... 其他代码不变 ... */}
    </div>
  )
})

说明

  • 改用状态控制UI后,每个子组件的模态框状态独立,不会再出现操作错组件的问题
  • 遵循React状态驱动UI的原则,避免直接操作DOM,确保状态和UI同步

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 18:15:13