'use client'; import { useEffect, useState } from 'react'; import useWindowsSize from './useWindowSize'; import { useMediaQuery, useTheme } from '@mui/material'; import { IS_SERVER } from '@/utils'; export const MOBILE_SCREEN_MAX_WIDTH = 600; // Sync with https://mui.com/material-ui/customization/breakpoints/ export const SERVER_SIDE_MOBILE_FIRST = true; // true - for mobile, false - for desktop /** * Hook to detect onMobile vs. onDesktop using "resize" event listener * @returns {boolean} true when on onMobile, false when on onDesktop */ export function useIsMobileByWindowsResizing() { const theme = useTheme(); const { width } = useWindowsSize(); const onMobile = width <= (theme.breakpoints?.values?.sm ?? MOBILE_SCREEN_MAX_WIDTH); return onMobile; } /** * Hook to detect onMobile vs. onDesktop using Media Query * @returns {boolean} true when on onMobile, false when on onDesktop */ function useIsMobileByMediaQuery() { // const onMobile = useMediaQuery({ maxWidth: MOBILE_SCREEN_MAX_WIDTH }); const theme = useTheme(); const onMobile = useMediaQuery(theme.breakpoints.down('sm')); return onMobile; } /** * Hook to detect onMobile vs. onDesktop with Next.js workaround * @returns {boolean} true when on onMobile, false when on onDesktop */ function useIsMobileForNextJs() { // const onMobile = useOnMobileByWindowsResizing(); const onMobile = useIsMobileByMediaQuery(); const [onMobileDelayed, setOnMobileDelayed] = useState(SERVER_SIDE_MOBILE_FIRST); useEffect(() => { // Defer the media-query result to after mount so SSR renders the mobile-first value // and the client reconciles without a hydration mismatch. Deliberate setState. // eslint-disable-next-line react-hooks/set-state-in-effect setOnMobileDelayed(onMobile); }, [onMobile]); return onMobileDelayed; } /** * Hook to apply "onMobile" vs. "onDesktop" class to document.body depending on screen size. * Due to SSR/SSG we can not set 'app-layout onMobile' or 'app-layout onDesktop' on the server * If we modify className using JS, we will got Warning: Prop `className` did not match. Server: "app-layout" Client: "app-layout onDesktop" * So we have to apply document.body.class using the hook :) * Note: Use this hook one time only! In main App or Layout component */ function useMobileOrDesktopByChangingBodyClass() { // const onMobile = useOnMobileByWindowsResizing(); const onMobile = useIsMobileByMediaQuery(); useEffect(() => { if (onMobile) { document.body.classList.remove('onDesktop'); document.body.classList.add('onMobile'); } else { document.body.classList.remove('onMobile'); document.body.classList.add('onDesktop'); } }, [onMobile]); } /** * We need a "smart export wrappers", because we can not use hooks on the server side */ // export const useOnMobile = IS_SERVER ? () => SERVER_SIDE_IS_MOBILE_VALUE : useOnMobileByWindowsResizing; // export const useOnMobile = IS_SERVER ? () => SERVER_SIDE_IS_MOBILE_VALUE : useOnMobileByMediaQuery; export const useIsMobile = IS_SERVER ? () => SERVER_SIDE_MOBILE_FIRST : useIsMobileForNextJs; export const useBodyClassForMobileOrDesktop = IS_SERVER ? () => undefined : useMobileOrDesktopByChangingBodyClass;