Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error: useMediaQuery is a client-only hook #218

Closed
jamesvclements opened this issue Aug 15, 2023 · 4 comments
Closed

Error: useMediaQuery is a client-only hook #218

jamesvclements opened this issue Aug 15, 2023 · 4 comments

Comments

@jamesvclements
Copy link

What is the proper way to use useMediaQuery in a Next.js site? I thought adding a check for window presence and providing defaults would help, but the Error: useMediaQuery is a client-only hook is still thrown from the useMediaQuery hook, where it runs the getServerSnapshot.

export function useDeviceInputType(): UseDeviceInputTypeReturnType {
    /* Default SSR to desktop */
    if (typeof window === 'undefined') {
        return {
            isLaptopOrDesktop: true,
            isLikelyTouch: false,
            isCellphone: false,
        };
    }

    const isLaptopOrDesktop = useMediaQuery(
        deviceInputMediaQueries.isLaptopOrDesktop
    );
    const isLikelyTouch = useMediaQuery(deviceInputMediaQueries.isLikelyTouch);
    const isCellphone = useMediaQuery(deviceInputMediaQueries.isCellphone);

    return { isLaptopOrDesktop, isLikelyTouch, isCellphone };
}
@tylermcginnis
Copy link
Collaborator

You're getting this because useSyncExternalStore invokes that getServerSnapshot when it hydrates on the client. It does this because even if your code worked, you're going to get a server/client mismatch when the user is on anything but isLaptopOrDesktop. Instead, you'll want to do something like this using the useIsClient hook.

"use client"

import { useIsClient } from "@uidotdev/usehooks"

export function SomeComponent () {
  const isClient = useIsClient()
  
  if (isClient === false) {
    return null
  }
  
  return <ComponentThatUsesUseDeviceInputType />
}

This should also allow you to get rid of the window check inside of useDeviceInputType.

@gardner
Copy link

gardner commented Dec 1, 2023

If you found this looking for an answer, you can use this component:

ClientOnly.tsx

"use client";
/**
 * Hack to work around next.js hydration
 * @see https://github.com/uidotdev/usehooks/issues/218
 */
import React from 'react';
import { useIsClient } from "@uidotdev/usehooks"

type ClientOnlyProps = {
  children: React.ReactNode;
};

export const ClientOnly: React.FC<ClientOnlyProps> = ({ children }) => {
  const isClient = useIsClient();

  // Render children if on client side, otherwise return null
  return isClient ? <>{children}</> : null;
};

Then wrap your client components:

<ClientOnly>
  <MyComponentThatUsesHooks />
</ClientOnly>

@thebiltheory
Copy link

Would this work?

import type { useMediaQuery } from "@uidotdev/usehooks";
import dynamic from "next/dynamic";

const useMediaQueryClient = dynamic(
  () =>
    import("@uidotdev/usehooks").then((module) => {
      return module.useMediaQuery;
    }),
  {
    ssr: false,
  }
) as typeof useMediaQuery;

export default useMediaQueryClient;

@arihantverma
Copy link

@tylermcginnis I understand what you say in #218 (comment) is correct. But what I'm not able to understand is, is this – why, if I want to want to use useMediaQuery for a feature that is only as a UI enhancement for a particular width based on the media query match, do I have to give up SSR, by having to force my entire component to render on the client? I'm currently trying to use useMediaQuery in an astro site with a react component, and facing this confusion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants