vuetifyjs / nuxt-module

Zero-config Nuxt Module for Vuetify
https://nuxt.vuetifyjs.com/
MIT License
220 stars 22 forks source link

How to conditionally render a component with `useDisplay` composable (SSR)? #197

Open igorexa34314 opened 8 months ago

igorexa34314 commented 8 months ago

I'm trying to conditionally render the navigation menu in the 'v-app-bar' based on the viewport size. I am using the mobile property from useDisplay() composable. If it's false, I render the list of links, otherwise the burger icon is rendered. On the local development server everything works fine. But when deploying on netlify, on initial loading, the list of links appears for a moment and then changes to burger button. This behavior is only seen on mobile devices. I have ssrClientHints.viewportSize enabled.

What could this be related to? Maybe I don't quite understand how ClientHints work.

userquin commented 8 months ago

A few questions:

igorexa34314 commented 8 months ago
userquin commented 8 months ago

@igorexa34314 can you provide the Netlify URL and a link to the repo (if public) ?

igorexa34314 commented 8 months ago

https://github.com/userquin/vuetify-nuxt-module/assets/110349044/ef6d0899-830d-4984-b00e-24cd55121371

I fixed the code a little bit. Now I found that menu is rendered incorrectly when the viewport width is between my breakpoint and 980px. Although if the width is not in this range, everything works fine.

@userquin This is the website address - https://keksbot.xyz/

userquin commented 8 months ago

On my Chrome (Windows) it is working properly even resizing it to 820px (when enabling mobile view a small slide down transition is there: IIRC, on mobile there is a small bug about slide down in SSR, check https://github.com/vuetifyjs/vuetify/pull/15229)

The SSR state seems to be fine:

imagen

HTTP Request Headers:

imagen

igorexa34314 commented 8 months ago

@userquin Could you try opening the site on mobile phone (Android or iOS)?

userquin commented 8 months ago

On Chrome (Android) the items shown, I Will check it tmr via usb to check what's happening (can be a problem with css, vuetify or both)

userquin commented 8 months ago

@igorexa34314 can you provide the structure you're using? It seems the component to show the items using 1280px but changed to use 820px

If using VToolbar, what's the collapse property you're using?

Maybe it is a problem with mobile-breakpoint prop in the navigation drawer.

userquin commented 8 months ago

Try setting :mobile-breakpoint="820": https://github.com/vuetifyjs/vuetify/blob/master/packages/vuetify/src/components/VNavigationDrawer/VNavigationDrawer.tsx#L107

EDIT: :mobile-breakpoint="819"

userquin commented 8 months ago

The server sending the toolbar with the hamburger, so it seems a problem with the breakpoints, maybe you can change the defaults using display property in vuetify options (update the entries, the code below from https://vuetifyjs.com/en/features/display-and-platform/#options):

display: {
    mobileBreakpoint: 'sm',
    thresholds: {
      xs: 0,
      sm: 340,
      md: 540,
      lg: 800,
      xl: 1280,
    },
  },

imagen

userquin commented 8 months ago

@igorexa34314 mobile breakpoint is 1280px, the default value is lg breakpoint: https://github.com/vuetifyjs/vuetify/blob/f8105c2902bc5ca57c92f8f2898c62a6d5fa001e/packages/vuetify/src/composables/display.ts#L86

igorexa34314 commented 8 months ago

@userquin I add breakpoint from app.config.ts using the 'vuetify:before-create' hook. I checked the breakpoints and everything is correct there.

export default defineNuxtPlugin(nuxtApp => {
  const { mobileBreakpoint } = useAppConfig()
  nuxtApp.hook('vuetify:before-create', ({ vuetifyOptions }) => {
    vuetifyOptions.display = {
      thresholds: {
        ...vuetifyOptions.display?.thresholds,
        lg: mobileBreakpoint
      },
      mobileBreakpoint: 'lg'
    }
  })
})

I also specify mobile breakpoint in useDisplay composable. My navigation bar looks like this:

<template>
  <v-app-bar
    class="app-navbar"
    flat
    color="base"
    :height="isMobile ? navbar.height.mobile : navbar.height.base"
    :elevation="0"
    style="width: 100%; z-index: 1006">
    <div
      class="navbar-container h-100 mx-auto"
      :class="[isMobile ? 'px-4 py-3' : 'pa-5']">
      <v-app-bar-title style="flex: 0 1 auto">
       //...
      </v-app-bar-title>

      <LazyUiBurgerBtn v-if="isMobile" />

      <LazyAppNavMenu v-else />
    </div>
  </v-app-bar>
</template>

<script setup lang="ts">
const { navbar } = useAppConfig().app
const isMobile = useIsMobile()

function useIsMobile = () => {
  const { mobileBreakpoint } = useAppConfig()
  const { mobile } = useVDisplay({ mobileBreakpoint })
  return mobile
}
</script>

I've also tried it without the lazy components as well.

userquin commented 8 months ago

do you have a VNavigationDrawer? It seems the problem is in the layout and the nav drawer, I can see the nav drawer styles in server response

userquin commented 8 months ago

If I don't use mobile view (size > 1280px) the server sending the page without nav drawer and hamburger, if the size is between 820px and 1280px I see the nav drawer styles (div + navigation-drawer classes) and the hamburger button.

userquin commented 8 months ago

if using size < 820px the server sending the nav drawer styles (div + navigation-drawer classes) and the hamburger button.

userquin commented 8 months ago

so, it seems a problem with the breakpoints, I don't know why your logic not working, the mobile break point seems to be fine:

imagen

igorexa34314 commented 8 months ago

I render the drawer when isMobile = true. And in mobile version when you click on the burger button, drawer should show up.

<!-- layouts/default.vue -->
<template>
  <v-layout class="app-layout d-flex flex-column">
    <LazyAppDrawer v-if="isMobile" v-model:drawer="drawer" />

    <AppNavbar  @burger-click="drawer = !drawer" />

    <v-main
      class="main pb-8"
      :style="{
        'padding-top': `${isMobile ? navbar.height.mobile : navbar.height.base}px`
      }">
      <Suspense>
        <slot />
      </Suspense>
    </v-main>

    <AppFooter />
  </v-layout>
</template>

<script setup lang="ts">
const { navbar } = useAppConfig().app

const isMobile = useIsMobile()
const drawer = ref(false)

watch(() => !isMobile.value,  () => {
    drawer.value = false
})
</script>
<!-- AppDrawer.vue -->
<template>
  <v-navigation-drawer
    v-model="drawer"
    location="top"
    :mobile-breakpoint="mobileBreakpoint"
    temporary>
    <AppNavMenu class="drawer-menu pt-6" />
  </v-navigation-drawer>
</template>

<script setup lang="ts">
const drawer = defineModel<boolean>('drawer', { default: false })

const { mobileBreakpoint } = useAppConfig()
</script>
userquin commented 8 months ago

we need to figure out what's happening with sizes between 820px and 1280px, maybe a bug in vuetify maybe a bug in the breakpoints or maybe both ;)

userquin commented 8 months ago

The mobile logic seems to fine: https://github.com/vuetifyjs/vuetify/blob/f8105c2902bc5ca57c92f8f2898c62a6d5fa001e/packages/vuetify/src/composables/display.ts#L169-L207

But, you're modifying only lg and maybe you're breaking the global breakpoints... check the logic in previous link.

userquin commented 8 months ago

can you just change mobile-breakpoint in the plugin with the value in the app config?

export default defineNuxtPlugin(nuxtApp => {
  const { mobileBreakpoint } = useAppConfig()
  nuxtApp.hook('vuetify:before-create', ({ vuetifyOptions }) => {
    vuetifyOptions.display = { mobileBreakpoint }
  })
})
userquin commented 8 months ago

You can use useNuxtApp().$vuetify.display.mobile.value in the console resizing the window

I'm going to check the logic nav drawer, can you show me the layout.vue?

userquin commented 8 months ago

ok, please apply my suggestion in the plugin, you're breaking the breakpoins: https://github.com/vuetifyjs/vuetify/blob/f8105c2902bc5ca57c92f8f2898c62a6d5fa001e/packages/vuetify/src/composables/display.ts#L170-L175

EDIT: remove also the useIsMobile function and use useVDisplay().mobile

igorexa34314 commented 8 months ago
function useIsMobile = () => {
  const { mobile } = useVDisplay()
  return mobile
}

Is it possible to make it like that to not override the default thresholds if there are any?

export default defineNuxtPlugin(nuxtApp => {
  const { mobileBreakpoint } = useAppConfig()
  nuxtApp.hook('vuetify:before-create', ({ vuetifyOptions }) => {
    vuetifyOptions.display ??= {}
    vuetifyOptions.display.mobileBreakpoint = mobileBreakpoint
  })
})
userquin commented 8 months ago

you can try both approach, vuetify will merge it, but yeah, that's also fine

userquin commented 8 months ago

still there, mobile breakpoint changed but server sending wrong html

userquin commented 8 months ago

can you initialize drawer properly in layout.vue?

<!-- layouts/default.vue -->
<template>
...
</template>
<script setup lang="ts">
const { navbar } = useAppConfig().app

const isMobile = useIsMobile()
const drawer = ref(isMobile.value)

watch(() => !isMobile.value,  () => {
    drawer.value = false
})
</script>
igorexa34314 commented 8 months ago

You want it to show up immediately on mobile?

userquin commented 8 months ago

upps, I read v-if="drawer"...

userquin commented 8 months ago

I'm going to prepare a reproduction with minimal configuration... do you have a discord account?

igorexa34314 commented 8 months ago

If I ask the repo owner to give you access, would it be better for you?

userquin commented 8 months ago

sure

igorexa34314 commented 8 months ago

Just don't be alarmed if you see a lot of shitty code ;)

userquin commented 8 months ago

do you have discord account?

igorexa34314 commented 8 months ago

Invite sent. My discord nickname is the same as my github username.

userquin commented 8 months ago

this one? imagen

userquin commented 8 months ago

SSR Client hints server plugin should be the last one, since we're using order: -25 the vuetify options configured in any app plugin will be ignored, it should have order: 24.

The client plugin should be the first one (the state is there)