点击Input关闭模态框时useState两次调用的问题求助
问题分析与解决方案
问题根源
当你再次点击Input试图关闭模态框时,会出现两次setIsShow调用的冲突:
- 自定义hook给document绑定了捕获阶段的click事件(第三个参数
true),点击Input时,document的click事件会优先触发。此时Input不在模态框的ref范围内,hook的事件处理逻辑会先调用setIsShow(false)。 - 随后事件传递到Input本身,触发它的
onClick回调,调用setIsShow(true)。 - 两次状态更新抵消后,
isShow最终还是true,导致模态框无法关闭。
另外,你的hook存在一个小问题:useEffect没有设置依赖数组,每次组件渲染都会重复绑定/解绑document的click事件,可能引发不必要的性能损耗。
修复方案
方案一:让Input点击事件切换状态(最简单)
直接修改Input的onClick回调,从强制设置true改为切换当前状态,这样无论hook的事件先触发什么,最终都会得到正确的开闭效果:
<input onClick={() => setIsShow(prev => !prev)} type="button" value={ `${adults === 1 ? `${adults} взрослый` : `${adults} взрослых`} · ${child === 0 ? 'Без детей' : child === 1 ? `${child} ребенок` : child < 5 ? `${child} ребенка` : `${child} детей`} · ${rooms === 1 ? `${rooms} номер` : rooms < 5 ? `${rooms} номера` : `${rooms} номеров`}` } />
方案二:优化自定义hook,跳过触发元素的关闭逻辑
在hook中新增对触发元素(Input)的判断,点击它时不执行关闭逻辑,同时修正useEffect的依赖问题:
import { useEffect, useRef, useState } from "react"; export default function useOutsideElement(initialVisible: boolean) { const [isShow, setIsShow] = useState<boolean>(initialVisible) const ref = useRef<HTMLDivElement>(null) // 存储触发元素的ref const triggerRef = useRef<HTMLInputElement | null>(null) const clickHandler = (e: MouseEvent) => { // 点击的是触发元素,直接跳过关闭逻辑 if (triggerRef.current === e.target) return if (ref.current && !ref.current.contains(e.target as Node)) { setIsShow(false) } } useEffect(() => { document.addEventListener('click', clickHandler, true) return () => { document.removeEventListener('click', clickHandler, true) } }, []) // 空依赖数组,仅绑定一次事件 return { ref, isShow, setIsShow, triggerRef } }
然后在组件中把Input的ref传给hook:
const PersonSearchBlock: NextPage = () => { const { ref, isShow, setIsShow, triggerRef } = useOutsideElement(false) // ... 其余状态不变 return ( <div className='shelters_housing_space_container_search_bar_person'> <div className='shelters_housing_space_container_search_bar_person_contant'> {/* ... 图片部分不变 */} <input ref={triggerRef} onClick={() => setIsShow(true)} type="button" // ... 其余代码不变 /> </div> {/* ... 模态框部分不变 */} </div> ) }
额外注意事项
要确保PersonModal组件正确接收并绑定refFunck到自身的根DOM元素上,否则可能出现点击模态框内部也会关闭的问题。可以用forwardRef实现:
import { forwardRef } from 'react' const PersonModal = forwardRef((props, ref) => { return ( <div ref={ref} className='your-modal-class'> {/* 模态框内容 */} </div> ) })
内容的提问来源于stack exchange,提问作者Стас Лабанов
相关产品推荐
相关产品推荐

