You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>
  );
};

问题根源分析

  1. 组件渲染分支导致ref未挂载:当组件处于loading、error或无data状态时,会直接返回<p>这类替代元素,此时带ref的div根本没被渲染,myDivRef.current自然是null。而useEffect在组件挂载后就会执行,这时候如果还在loading阶段,就拿不到ref元素。

  2. useEffect依赖引发的重复执行:依赖数组里的state每次更新都会触发useEffect重新执行,要是此时ref还没挂载,就会跳过事件绑定;同时之前的事件解绑可能因为ref为null没执行到位,导致事件绑定混乱。

  3. 闭包导致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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 05:22:45