仿CoinMarketCap搜索框onBlur异常触发问题求助
解决搜索框容器onBlur异常触发的问题
问题原因
- div容器默认无焦点能力:你给
search-box这个div加了onBlur,但div默认无法获得焦点,除非设置tabIndex属性,否则onBlur事件根本不会按预期工作。 - 内部元素抢焦点触发onBlur:就算给div加了tabIndex,当点击容器内的输入框、列表项时,焦点会从容器转移到内部元素,导致容器触发onBlur,错误关闭搜索框。
- 当前onBlur逻辑完全错误:你用setTimeout返回的定时器ID判断
if(temp),定时器ID永远是正数,所以不管怎样都会执行setSearch(!search),直接导致搜索框一触发onBlur就关闭。
正确解决方案
方案1:用点击外部检测替代onBlur(推荐)
这种方式更可靠,不会受内部元素焦点影响,步骤如下:
- 给组件添加useRef获取搜索框容器:
import { useState, useRef, useEffect } from 'react'; // ... const searchBoxRef = useRef(null);
- 监听document的点击事件,判断点击区域是否在搜索框外部:
useEffect(() => { const handleClickOutside = (e) => { if (searchBoxRef.current && !searchBoxRef.current.contains(e.target)) { setSearch(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [search]);
- 修改真实搜索框的代码,把ref绑定到容器上,同时移除原来错误的onBlur:
{search ? ( <div ref={searchBoxRef} id="search-box" className="absolute bg-white w-full h-fit border border-slate-200 rounded-2xl shadow-sm" > <!-- 内部内容保持不变 --> </div> ) : ( "" )}
方案2:优化onBlur逻辑(仅作参考,不推荐)
如果一定要用onBlur,需要给容器加tabIndex,并且处理内部元素的焦点转移:
<div tabIndex="0" onBlur={(e) => { // 检查焦点是否转移到了容器内部的元素 if (!e.currentTarget.contains(e.relatedTarget)) { setSearch(false); } }} id="search-box" className="absolute bg-white w-full h-fit border border-slate-200 rounded-2xl shadow-sm" > <!-- 内部内容保持不变 --> </div>
优化自动聚焦逻辑
原来的setInterval容易导致内存泄漏,改用useEffect更安全:
useEffect(() => { if (search) { const input = document.getElementById("input-search"); input?.focus(); } }, [search]); // 假输入框的onClick里移除setInterval,只保留状态切换: onClick={() => setSearch(!search)}
最终关键代码片段
import { useState, useRef, useEffect } from 'react'; function SearchComponent() { const [search, setSearch] = useState(false); const searchBoxRef = useRef(null); // 点击外部关闭搜索框 useEffect(() => { const handleClickOutside = (e) => { if (searchBoxRef.current && !searchBoxRef.current.contains(e.target)) { setSearch(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, [search]); // 自动聚焦输入框 useEffect(() => { if (search) { const input = document.getElementById("input-search"); input?.focus(); } }, [search]); return ( <> {/* 假输入框(内部内容不变) */} <div className="bg-slate-300 rounded-lg flex w-96 h-14 items-center justify-between cursor-pointer" onClick={() => setSearch(!search)} > <div className="flex m-3"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" className="bi bi-search" viewBox="0 0 16 16" > <path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z" /> </svg> <div className="font-bold mx-5 text-slate-600">Search</div> </div> <div className="bg-slate-400 w-8 text-center rounded text-white mr-3"> / </div> </div> {/* 真实搜索框(内部内容不变) */} {search ? ( <div ref={searchBoxRef} id="search-box" className="absolute bg-white w-full h-fit border border-slate-200 rounded-2xl shadow-sm" > <div className="m-5"> <div className="flex justify-between "> <i className="mx-2 bi bi-search"></i> <input id="input-search" className="w-full focus:outline-none " type="text" placeholder="Search coin, pair, contract address or exchange" maxLength={200} ></input> <i className="mx-2 bi bi-x-circle cursor-pointer"></i> </div> <div className=" p-5"> <div className="flex text-xl"> <div>Trending</div> <i onClick={() => { let temp = document.getElementById("input-search"); console.log(temp); }} className="bi bi-fire text-orange-600" ></i> </div> <div> {rowData ? ( rowData.map((coin) => { return ( <div onClick={() => { navigate("coin/" + coin?.symbol); }} className="flex justify-between cursor-pointer"> <div className="flex items-center"> <img className="m-2 w-10 h-10" src={coin?.logo} alt="logo" /> <div className="m-2 font-bold"> {coin?.name} </div> <div className="m-2 text-slate-400"> {coin?.symbol} </div> </div> <div> <div className="text-slate-400 text-lg"> # {coin?.id} </div> </div> </div> ); }) ) : ( <Loading /> )} </div> </div> <div className="border-t-2 border-slate-200 p-5"> <div className="text-xl">Recent searches</div> </div> </div> </div> ) : null} </> ); }
内容的提问来源于stack exchange,提问作者StupidCat
相关产品推荐
相关产品推荐

