react-bootstrap模态框内react-popper中select点击后弹窗秒消失问题
问题:React-Bootstrap Modal中Popper内的Select弹窗秒消失
在React-Bootstrap Modal中使用react-popper展示带年月选择的DatePicker时,点击select的下拉选项弹窗仅显示一秒就消失。
复现代码
TSX代码:
import React, {useState} from 'react'; import ReactDOM from 'react-dom'; import FocusTrap from 'focus-trap-react'; import { usePopper } from 'react-popper'; export default function Tooltip() { const [referenceElement, setReferenceElement] = useState(null); const [popperElement, setPopperElement] = useState(null); const [showPicker, setShowPicker] = useState(false); const popper = usePopper(referenceElement, popperElement, { placement: 'bottom-start' }); const openPopper = () => { setShowPicker(true); }; const closePopper = () => { setShowPicker(false); }; return <div className='datepicker-container'> <a ref={setReferenceElement} className="btn btn-dark" onClick={openPopper}>Click to open select in Popper</a> {showPicker && ReactDOM.createPortal( <FocusTrap active focusTrapOptions={{ initialFocus: false, allowOutsideClick: true, clickOutsideDeactivates: true, onDeactivate: closePopper }}> <div tabIndex={-1} style={popper.styles.popper} className="dialog-sheet" {...popper.attributes.popper} ref={setPopperElement} role="dialog"> <div> <select> <option>1</option> <option>2</option> <option>3</option> <option>4</option> </select> </div> </div> </FocusTrap>, document.querySelector('#popup-container') )} </div> }
CSS代码:
.datepicker-container { position: relative; z-index: 100; } .dialog-sheet { z-index: 2003; background-color: #EEE; }
原因分析
问题核心在于FocusTrap的配置冲突:
- 你设置了
clickOutsideDeactivates: true,而原生select的下拉弹窗是浏览器渲染的独立元素,不属于FocusTrap包裹的DOM范围,点击下拉选项时会被判定为“点击外部”,触发onDeactivate关闭Popper。 - React-Bootstrap Modal本身自带焦点陷阱(Focus Trap),额外嵌套FocusTrap会导致双重焦点管理,加剧冲突。
解决方案
方案一:调整FocusTrap的点击判定逻辑
修改clickOutsideDeactivates为自定义函数,仅当点击Popper外部区域时才触发关闭:
<FocusTrap active focusTrapOptions={{ initialFocus: false, allowOutsideClick: true, clickOutsideDeactivates: (e) => { // 判断点击目标是否属于Popper容器内的元素 return !e.target.closest('.dialog-sheet'); }, onDeactivate: closePopper }}> {/* 原有Popper内容 */} </FocusTrap>
方案二:移除额外的FocusTrap,改用外部点击监听
利用Modal自带的焦点管理,去掉FocusTrap组件,通过监听document点击事件来关闭Popper:
import React, { useState, useEffect } from 'react'; import ReactDOM from 'react-dom'; import { usePopper } from 'react-popper'; export default function Tooltip() { const [referenceElement, setReferenceElement] = useState(null); const [popperElement, setPopperElement] = useState(null); const [showPicker, setShowPicker] = useState(false); const popper = usePopper(referenceElement, popperElement, { placement: 'bottom-start' }); const openPopper = () => { setShowPicker(true); }; const closePopper = () => { setShowPicker(false); }; // 监听外部点击关闭Popper useEffect(() => { if (!showPicker) return; const handleClickOutside = (e) => { const isClickInsidePopper = popperElement?.contains(e.target); const isClickOnTrigger = referenceElement?.contains(e.target); if (!isClickInsidePopper && !isClickOnTrigger) { closePopper(); } }; document.addEventListener('click', handleClickOutside); return () => document.removeEventListener('click', handleClickOutside); }, [showPicker, popperElement, referenceElement]); // 阻止Popper内部点击冒泡到document const handlePopperClick = (e) => { e.stopPropagation(); }; return ( <div className='datepicker-container'> <a ref={setReferenceElement} className="btn btn-dark" onClick={openPopper}>Click to open select in Popper</a> {showPicker && ReactDOM.createPortal( <div tabIndex={-1} style={popper.styles.popper} className="dialog-sheet" {...popper.attributes.popper} ref={setPopperElement} role="dialog" onClick={handlePopperClick} > <div> <select> <option>1</option> <option>2</option> <option>3</option> <option>4</option> </select> </div> </div>, document.querySelector('#popup-container') )} </div> ); }
补充说明
你的CSS中z-index设置没有问题,dialog-sheet的2003已经高于React-Bootstrap Modal默认的1050,无需调整。
内容的提问来源于stack exchange,提问作者Martin Cup
相关产品推荐
相关产品推荐

