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> ) }
问题根源
- 重复DOM ID冲突:每个子组件的模态框都用了相同的
id="my-modal",调用document.getElementById时只会返回第一个匹配的DOM元素,导致所有删除按钮操作的都是第一个子组件的模态框 - 直接操作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
相关产品推荐
相关产品推荐

