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

NextJS 15 App Router客户端组件首次加载失效求助

NextJS 15 App Router Dropdown首次加载无响应问题解决

问题现象

使用NextJS 15 App Router开发时,Dropdown客户端组件首次加载点击无响应,组件内控制台日志不输出,刷新页面后功能恢复正常。

问题原因

  1. useOutsideClick钩子的useEffect依赖项不全,闭包导致事件监听逻辑在首次加载时未正确绑定
  2. 服务端渲染与客户端hydration过程中,事件处理函数存在绑定异常风险

解决方案

1. 完善useOutsideClick钩子的依赖项

修改useOutsideClick的useEffect依赖数组,补充ref和callback,避免闭包引发的逻辑失效:

import { useEffect } from "react";

function useOutsideClick<T extends HTMLElement>(ref: React.RefObject<T | null>, isShowing: boolean, callback: () => void) {
    useEffect(() => {
        function handleClickOutside(event: globalThis.MouseEvent) {
            if (ref.current && !ref.current.contains(event.target as Node) && isShowing) {
                callback();
            }
        }

        document.addEventListener("mousedown", handleClickOutside);

        return () => {
            document.removeEventListener("mousedown", handleClickOutside);
        };
    }, [ref, isShowing, callback]); // 添加ref和callback到依赖数组
}

export default useOutsideClick;

2. 确保Dropdown组件在客户端hydration完成后响应事件

添加状态标记组件是否完成hydration,避免hydration不匹配导致的事件失效:

"use client";

import useOutsideClick from "@/Utils";
import React, { useRef, useState, useEffect } from "react"
import DownArrowSingle from "./DownArrowSingle";

interface PropType {
    children: React.ReactNode,
    label: string,
    labelColor?: string,
    bgColor?: string,
}

export default function DropDown({ children, label, bgColor, labelColor }: PropType) {

    const [isVisible, setIsVisible] = useState<boolean>(false);
    const [isHydrated, setIsHydrated] = useState(false);
    const dropDownRef = useRef<HTMLDivElement | null>(null);

    const finalColor = bgColor || '#FFF';
    const finalLabelColor = labelColor || '#000';

    useEffect(() => {
        setIsHydrated(true);
    }, []);

    useOutsideClick(dropDownRef, isVisible, () => setIsVisible(false));

    console.log("IS DROPDOWN VISIBLE -> ", isVisible);

    return (
        <div
            ref={dropDownRef}
            style={{ backgroundColor: finalColor, color: finalLabelColor }}
            onClick={() => isHydrated && setIsVisible(prev => !prev)}
            className="cursor-pointer relative flex justify-center items-center gap-2 font-semibold rounded-full px-4 py-2 text-lg">

            {label}
            <DownArrowSingle />

            <ul
                onClick={e => e.stopPropagation()}
                className={`absolute ${isVisible ? 'flex' : 'hidden'} w-[400px] flex-col justify-start items-stretch left-0 top-[calc(100%+10px)] bg-[#0a0909] rounded-lg`}
            >
                {children}
            </ul>
        </div>
    )
}

3. 确认组件渲染逻辑合法性

Header组件已声明"use client",在服务端渲染的根布局中直接导入是合法的,无需额外修改。

效果验证

修改后,组件首次加载时会正确完成客户端hydration,事件监听逻辑正常绑定,Dropdown点击和日志输出功能无需刷新页面即可正常工作。

内容的提问来源于stack exchange,提问作者The Noobest Guy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 06:17:11