NextJS中React Hook useRef失效求助:刷新后Ref值为null
NextJS中useRef.current始终为null的问题解决
在NextJS应用里用useRef时碰到个问题:页面刷新后myDivRef.current一直是null,导致相关的if逻辑根本跑不起来,控制台打印"Ref: null"。猜测问题可能和useEffect首次渲染、依赖数组里的state和fetchMore引发的重复执行有关,原代码如下:
function DisplayLoadMore() { const [state, setState] = useState(0); const { loading, error, data, fetchMore} = useQuery(GET_DATA,{ variables: { cursor: 0, limit: 40 }, }); const myDivRef = useRef<HTMLDivElement | null>(null); useEffect(() => { const handleScroll = () => { if (myDivRef.current) { console.log("True"); const isAtBottom = myDivRef.current.scrollTop + myDivRef.current.clientHeight === myDivRef.current.scrollHeight; if (isAtBottom) { console.log("Hit bottom"); setState((state) => { return state + 40; }); fetchMore({ variables: { cursor: state, limit: 40 }, }); } } }; if (myDivRef.current) { myDivRef.current.addEventListener('scroll', handleScroll); } return () => { if (myDivRef.current) { myDivRef.current.removeEventListener('scroll', handleScroll); } }; }, [fetchMore, state]); try{ if (loading) return <p>Loading...</p>; if (error) return <p>Error!!!</p>; if (!data) return <p>Not found</p>; }catch (error: any){ console.error('An error occurred:', console.error(error)); }finally{ console.log('...'); } return ( <div ref={myDivRef} style={{ overflowY: 'scroll', height: '700px' }}> {data.pagedbs.map(({ id, name }: { id: number; name: string }) => ( <div key={id}> <p>{name}</p> </div> ))} </div> ); } export default function App() { return ( <div > <DisplayLoadMore/> </div> ); };
问题根源分析
组件渲染分支导致ref未挂载:当组件处于
loading、error或无data状态时,会直接返回<p>这类替代元素,此时带ref的div根本没被渲染,myDivRef.current自然是null。而useEffect在组件挂载后就会执行,这时候如果还在loading阶段,就拿不到ref元素。useEffect依赖引发的重复执行:依赖数组里的
state每次更新都会触发useEffect重新执行,要是此时ref还没挂载,就会跳过事件绑定;同时之前的事件解绑可能因为ref为null没执行到位,导致事件绑定混乱。闭包导致fetchMore拿不到最新state:
handleScroll里调用fetchMore时用的是外层的state,闭包会捕获当前渲染周期的state值,这时候拿到的可能不是最新的state,导致cursor参数错误。
修正后的代码
function DisplayLoadMore() { const [state, setState] = useState(0); const { loading, error, data, fetchMore } = useQuery(GET_DATA, { variables: { cursor: 0, limit: 40, }, }); const myDivRef = useRef<HTMLDivElement | null>(null); useEffect(() => { // 先拿到ref元素,不存在直接返回 const scrollElement = myDivRef.current; if (!scrollElement) return; const handleScroll = () => { const isAtBottom = scrollElement.scrollTop + scrollElement.clientHeight === scrollElement.scrollHeight; if (isAtBottom) { console.log("Hit bottom"); // 用函数式更新获取最新state,同时传递给fetchMore setState((prevState) => { const newCursor = prevState + 40; fetchMore({ variables: { cursor: newCursor, limit: 40 } }); return newCursor; }); } }; scrollElement.addEventListener("scroll", handleScroll); // 解绑时直接用已拿到的元素,无需再判断ref return () => scrollElement.removeEventListener("scroll", handleScroll); }, [fetchMore]); // 移除state依赖,避免不必要的重执行 // 直接判断渲染状态,移除冗余的try/catch if (loading) return <p>Loading...</p>; if (error) return <p>Error!!!</p>; if (!data) return <p>Not found</p>; return ( <div ref={myDivRef} style={{ overflowY: "scroll", height: "700px" }}> {data.pagedbs.map(({ id, name }: { id: number; name: string }) => ( <div key={id}> <p>{name}</p> </div> ))} </div> ); } export default function App() { return ( <div> <DisplayLoadMore /> </div> ); }
关键修正点
- 移除冗余的try/catch:原代码里的try块包裹渲染条件判断完全没必要,这些逻辑不会抛出异常,反而会干扰组件正常渲染流程。
- 确保ref存在再执行逻辑:在useEffect里先获取ref元素,不存在则直接返回,避免后续的null判断。
- 优化事件绑定与解绑:绑定事件时先存好元素引用,解绑时直接用这个引用,确保事件能正确移除,不会出现内存泄漏。
- 修复闭包导致的state过时问题:用setState的函数式更新方式,直接在内部计算最新的cursor值并传给fetchMore,保证拿到的是最新状态。
- 调整useEffect依赖数组:移除state依赖,因为我们通过函数式更新获取最新state;只保留fetchMore,确保useQuery的fetchMore更新时能正确重新绑定事件。
内容的提问来源于stack exchange,提问作者Muhammad Anas
相关产品推荐
相关产品推荐

