错误:useMediaQuery 是仅限客户端的挂钩

Gau*_*ang 9 reactjs next.js next.js13

我正在尝试使用 NextJS 13.4.7 创建一个应用程序,并使用useMediaQuery钩子来检测视图是否是移动的。我的组件不是服务器组件,但我不断收到client-only挂钩错误。现在我的客户端组件是服务器组件的一部分,但从文档来看,它似乎是允许的。我错过了什么吗?下面是代码,

"use client";

import { useContext, useState, useEffect } from "react";
import PageDataContext from "@/contexts/PageDataContext";
import useMediaQuery from "@/hooks/useMediaQuery";
import { motion } from "framer-motion";

export default function Header() {
  const content = useContext(PageDataContext);
  const isSmallDevice = useMediaQuery("only screen and (max-width : 991px)");
  const [hideNav, setHideNav] = useState(isSmallDevice);
  const { home: homeContent } = content;
  const { navigation: navigationContent } = content;
  const { name } = homeContent;
  const [firstName, lastName] = name.split(" ");

  const onTheRight = { x: "100%" };
  const inTheCenter = { x: 0 };
  const onTheLeft = { x: "-100%" };

  const transition = { duration: 0.6, ease: "easeInOut" };

  const variants = {
    open: { opacity: 1, x: "0%" },
    closed: { opacity: 0, x: "100%" },
  };
  const toggleSideNav = () => {
    setHideNav(!hideNav);
  };

  useEffect(() => {
    setHideNav(isSmallDevice);
  }, [isSmallDevice]);

  return (
    <header id="site_header" className="header">
      <div className="header-content clearfix">
        <div className="text-logo">
          <a href="/">
            <div className="logo-symbol">{firstName[0].toUpperCase()}</div>
            <div className="logo-text">
              {firstName} <span>{lastName}</span>
            </div>
          </a>
        </div>
        <motion.div
          animate={hideNav ? "closed" : "open"}
          variants={variants}
          style={{
            position: "fixed",
            width: "100%",
            height: "calc(100% - 52px)",
            top: "52px",
            left: "auto",
          }}
        >
          <div className="site-nav">
            <ul className="leven-classic-menu site-main-menu">
              {navigationContent.map((item, idx) => (
                <li className="menu-item" key={"header_key_" + idx}>
                  <a href={item.url}>{item.title}</a>
                </li>
              ))}
            </ul>
          </div>
        </motion.div>
        <a className="menu-toggle mobile-visible" onClick={toggleSideNav}>
          <i className="fa fa-bars"></i>
        </a>
      </div>
    </header>
  );
}
Run Code Online (Sandbox Code Playgroud)

mel*_*hin 0

我遇到了同样的问题。这是因为 useMediaQuery 同时渲染服务器端和客户端。即使您给出“use client”指令,如果它以某种方式在 ssr 所在的位置工作,也会发生此错误。

作为解决方案,您可以创建自定义挂钩并通过动态导入使用该挂钩:

自定义挂钩示例:

 "use client";
import useMediaQuery from "@/hooks/useMediaQuery";

const useDevice = () => {
  const isMobile = useMediaQuery("only screen and (max-width : 767px)");
  const isTablet = useMediaQuery(
    "only screen and (min-width : 768px) and (max-width : 1024px)"
  );
  const isDesktop = useMediaQuery(
    "only screen and (min-width : 1025px) and (max-width : 2379px)"
  );
  const isDesktopLarge = useMediaQuery("only screen and (min-width : 2380px)");

  return { isMobile, isTablet, isDesktop, isDesktopLarge };
};

export default useDevice;
Run Code Online (Sandbox Code Playgroud)

用法:

"use client";
const useDevice = dynamic(() => import("@/app/hooks/useDevice"), {
  ssr: false
});


export default function Header() {
  const device = useDevice();
  console.log(device?.isDesktop)

  ...

}
Run Code Online (Sandbox Code Playgroud)