Skip to content

fix: avoid re-render in useMobile#10433

Open
iloveitaly wants to merge 2 commits intoshadcn-ui:mainfrom
iloveitaly:use-mobile-fix
Open

fix: avoid re-render in useMobile#10433
iloveitaly wants to merge 2 commits intoshadcn-ui:mainfrom
iloveitaly:use-mobile-fix

Conversation

@iloveitaly
Copy link
Copy Markdown

Description

This PR resolves a React warning triggered by the useIsMobile hook:

Warning: Calling setState synchronously within an effect can trigger cascading renders.

This was added with a recent linting update.

Currently, the hook initializes state as undefined and then synchronously calls setIsMobile on the first pass of the useEffect.
This forces React to immediately re-render the component after the initial paint, which can cause layout shifts and degrade performance.

Changes Made

  • Updated the useState initialization to lazily evaluate window.innerWidth so the correct boolean is set immediately on the first render.
  • Added a typeof window !== "undefined" check during state initialization to ensure it remains SSR-safe.
  • Removed the synchronous setIsMobile call from within the useEffect body. The initial state now handles the first render correctly, and the effect strictly acts as an event listener for subsequent window resizing.

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 18, 2026

@iloveitaly is attempting to deploy a commit to the shadcn-pro Team on Vercel.

A member of the Team first needs to authorize it.

@CHC383
Copy link
Copy Markdown

CHC383 commented Apr 18, 2026

I ran into the same issue while running eslint and got the set-state-in-effect error. Below is another option that achieves the same result but should avoid the hydration mismatch due to server rendering isMobile being different from the client ones

import { useSyncExternalStore } from "react";

export function useIsMobile(mobileBreakpoint = 768) {
  return useSyncExternalStore(
    (callback) => {
      const mql = globalThis.matchMedia(
        `(max-width: ${(mobileBreakpoint - 1).toFixed(0)}px)`,
      );
      mql.addEventListener("change", callback);
      return () => {
        mql.removeEventListener("change", callback);
      };
    },
    () => window.innerWidth < mobileBreakpoint, // Client value
    () => false, // Server/Initial hydration value
  );
}

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

Successfully merging this pull request may close these issues.

2 participants