React中使用usePopper在表格映射中仅渲染单个Popper的方法
解决Reactstrap表格映射中Popper批量触发的问题
核心问题原因
你目前用单个isOpen布尔值控制所有行的Popper显示,点击任何一行都会触发全局状态变更,导致所有Popper同时弹出。解决的核心思路是用行的唯一标识精准控制单个Popper的显示状态,而非依赖全局布尔值。
具体实现步骤
1. 修改状态管理
把原来的单个isOpen状态替换为activeRowId,用来记录当前需要显示Popper的行ID(初始值设为null):
const [activeRowId, setActiveRowId] = useState(null);
2. 编写状态切换逻辑
写一个切换函数,接收当前行的ID作为参数,判断是否要打开/关闭对应行的Popper:
const togglePopper = (rowId) => { // 如果当前行已激活则关闭,否则激活当前行 setActiveRowId(prev => prev === rowId ? null : rowId); };
3. 封装行组件(推荐)
把每行的内容和Popper逻辑封装成独立子组件,避免映射中重复代码,同时让每个行拥有独立的Popper实例(解决react-popper的引用冲突):
子组件TableRowWithPopper.jsx:
import { useRef, useState } from 'react'; import { usePopper } from 'react-popper'; export default function TableRowWithPopper({ item, isOpen, onToggle }) { // 每个行独立的引用 const referenceRef = useRef(null); const [popperElement, setPopperElement] = useState(null); // 初始化当前行的Popper实例 const { styles, attributes } = usePopper( referenceRef.current, popperElement, { placement: 'top', // 自定义Popper位置 modifiers: [{ name: 'offset', options: { offset: [0, 8] } }] } ); return ( <tr> {/* 触发Popper的单元格 */} <td ref={referenceRef} onClick={onToggle} style={{ cursor: 'pointer' }}> {item.name} {/* 仅当当前行激活时渲染Popper */} {isOpen && ( <div ref={setPopperElement} style={{ ...styles.popper, backgroundColor: '#fff', border: '1px solid #ddd', padding: '8px', borderRadius: '4px' }} {...attributes.popper} > {item.detail} </div> )} </td> <td>{item.value}</td> </tr> ); }
4. 在表格中映射组件
在父组件的表格中,遍历数据并传递对应的状态和事件:
import { Table } from 'reactstrap'; import TableRowWithPopper from './TableRowWithPopper'; export default function YourTable({ dataList }) { const [activeRowId, setActiveRowId] = useState(null); const togglePopper = (rowId) => { setActiveRowId(prev => prev === rowId ? null : rowId); }; return ( <Table striped bordered hover> <thead> <tr> <th>名称</th> <th>数值</th> </tr> </thead> <tbody> {dataList.map(item => ( <TableRowWithPopper key={item.id} item={item} isOpen={activeRowId === item.id} onToggle={() => togglePopper(item.id)} /> ))} </tbody> </Table> ); }
额外优化(可选)
如果需要点击页面其他区域自动关闭Popper,可以在父组件添加全局点击监听:
import { useEffect } from 'react'; // 父组件内添加 useEffect(() => { const handleClickOutside = (e) => { // 判断点击目标不在任何触发元素或Popper内时,关闭所有Popper const triggerElements = document.querySelectorAll('[data-popper-trigger]'); const popperElements = document.querySelectorAll('[data-popper-content]'); const isClickInside = [...triggerElements, ...popperElements].some(el => el.contains(e.target)); if (!isClickInside) setActiveRowId(null); }; document.addEventListener('click', handleClickOutside); return () => document.removeEventListener('click', handleClickOutside); }, []);
记得给触发元素和Popper元素加上对应的data-popper-trigger、data-popper-content属性。
内容的提问来源于stack exchange,提问作者giosan
相关产品推荐
相关产品推荐

