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

[Bug Report] Wrong first $vuetify.breakpoint value when using webpack-ssr #3436

Closed
peluprvi opened this issue Mar 1, 2018 · 30 comments · Fixed by #11842
Closed

[Bug Report] Wrong first $vuetify.breakpoint value when using webpack-ssr #3436

peluprvi opened this issue Mar 1, 2018 · 30 comments · Fixed by #11842
Assignees
Labels
framework Issues and Feature Requests that have needs framework-wide. Service: Breakpoint SSR SSR T: bug Functionality that does not work as intended/expected
Milestone

Comments

@peluprvi
Copy link
Contributor

peluprvi commented Mar 1, 2018

Versions and Environment

Vuetify: 1.0.4
Vue: 2.5.13
Browsers: Chrome 63.0.3239.132
OS: Windows 7

Steps to reproduce

Expected Behavior

Cards should be rendered side by side (row layout).

Actual Behavior

When hitting F5, cards are rendering one below the other (column layout) - that is the bug.
When resize the window, cards are rendering as expected (row layout).
When webpack compiles with the page previously opened, cards are rendering as expected (row layout).

Reproduction Link

https://codepen.io/anon/pen/rJRNrL

@johnleider johnleider added the pending review The issue is still pending disposition label Mar 5, 2018
@KaelWD KaelWD self-assigned this Mar 6, 2018
@KaelWD KaelWD added this to the Sprint 6 milestone Mar 6, 2018
@peluprvi
Copy link
Contributor Author

peluprvi commented Mar 6, 2018

It works when using a v-toolbar.

@johnleider
Copy link
Member

I have been unable to reproduce this error.

peluprvi added a commit to peluprvi/vuetify-issue-3436 that referenced this issue Mar 9, 2018
@peluprvi
Copy link
Contributor Author

peluprvi commented Mar 9, 2018

Versions and Environment

Vuetify: 1.0.5
Vue: 2.5.13
Browsers: Chrome 64.0.3282.186
OS: Windows 7

Reproduction repository

https://github.com/peluprvi/vuetify-issue-3436

After F5 (window width = 960px)

after-f5

After window resize (and expected behavior) (window width = 960px)

after-window-resize

@peluprvi
Copy link
Contributor Author

peluprvi commented Mar 9, 2018

Workaround

Use a computed prop that conditionally changes after nextTick on created hook:

<script>
  export default {
    props: {
      xsOnly: {
        type: Boolean,
        default: false
      }
    },

    data: () => ({
      reloaded: false
    }),

    computed: {
      isXsOnly () {
        return this.reloaded ? this.xsOnly || this.$vuetify.breakpoint.xsOnly : this.xsOnly
      }
    },

    created () {
      this.$nextTick(function () {
        this.reloaded = true
      })
    }
  }
</script>

@KaelWD
Copy link
Member

KaelWD commented Mar 9, 2018

As I said in discord, this appears to be vuejs/vue#7063 but with classes this time. Try adding a ref to one of those v-layouts and see what happens.

@peluprvi I would consider that bad advice, as it relies on timing. A better solution would be to use the mounted hook instead:

data: () => ({
  isHydrated: false
})

computed: {
  breakpoint () { // just an example, could be one specific value if that's all you need
    return this.isHydrated
      ? this.$vuetify.breakpoint
      : // "empty" $breakpoint object with initial values
  }
}

mounted () {
  this.isHydrated = true
}

@KaelWD
Copy link
Member

KaelWD commented Mar 10, 2018

I've opened a new issue vuejs/vue#7787, follow that for updates.

@KaelWD KaelWD removed this from the Sprint 6 milestone Mar 20, 2018
@KaelWD KaelWD added T: bug Functionality that does not work as intended/expected and removed pending review The issue is still pending disposition labels Jun 18, 2018
@KaelWD KaelWD added this to the v1.2.0 milestone Jun 18, 2018
@KaelWD KaelWD modified the milestones: v1.2.0, v1.3.0 Jul 31, 2018
@kovsky0
Copy link

kovsky0 commented Sep 27, 2018

Based on the response above I've made a simple nuxt plugin so that we don't have to use mounted hooks on every component that needs to use vuetify breakpoints

import Vue from 'vue'

Vue.prototype.$breakpoint = new Vue({
    data () {
        return {
            isMounted: false,
            default: {
                xs: true,
                xsOnly: true,
                xsAndUp: false,
                sm: true,
                smOnly: true,
                smAndDown: true,
                smAndUp: false,
                md: false,
                mdOnly: false,
                mdAndDown: false,
                mdAndUp: false,
                lg: false,
                lgOnly: false,
                lgAndDown: false,
                lgAndUp: false,
                xl: false,
                xlOnly: false,
                xlAndDown: false
            }
        }
    },
    methods: {
        is (breakpoint) {
            return this.isMounted ? this.$vuetify.breakpoint[breakpoint] : this.$data.default[breakpoint]
        }
    }
})

export default async function ({ app }) {
    if (!app.mixins) {
        app.mixins = []
    }
    app.mixins.push({
        mounted () {
            this.$breakpoint.$data.isMounted = true
        }
    })
}

@johnleider
Copy link
Member

This did not make the cut for v1.3 and is being refactored for v2.0

@begueradj
Copy link
Contributor

None of the suggested solutions worked for me (tried them all)

@blalan05 blalan05 added framework Issues and Feature Requests that have needs framework-wide. SSR SSR labels Apr 9, 2019
@JoshCoady
Copy link

JoshCoady commented May 5, 2019

This is a bit of a hack and not for everyone, but I worked around the issue by triggering a breakpoint recalc in mounted:

mounted() {
  this.$vuetify.clientHeight--
  this.$vuetify.clientHeight++
}

If you dont mind there being a minor difference in the actual value, this works also:

mounted() {
  this.$vuetify.clientHeight -= 0.1
}

@KRaymundus
Copy link

Added a computed function to @kovsky0 's nuxt-plugin so the call-syntax is a bit prettier:

import Vue from 'vue'

Vue.prototype.$breakpoint = new Vue({
  data() {
    return {
      isMounted: false,
      default: {
        xs: true,
        xsOnly: true,
        xsAndUp: true,
        sm: false,
        smOnly: true,
        smAndDown: true,
        smAndUp: false,
        md: false,
        mdOnly: false,
        mdAndDown: true,
        mdAndUp: false,
        lg: false,
        lgOnly: false,
        lgAndDown: true,
        lgAndUp: false,
        xl: false,
        xlOnly: false,
        xlAndDown: true,
      },
    }
  },
  computed: {
    is() {
      return Object.keys(this.$vuetify.breakpoint).reduce((breakpoints, dim) => {
        breakpoints[dim] = this.breakpointWithDefault(dim)
        return breakpoints
      }, {})
    },
  },
  methods: {
    breakpointWithDefault(breakpoint) {
      return this.isMounted ? this.$vuetify.breakpoint[breakpoint] : this.$data.default[breakpoint]
    },
  },
})

export default async function ({ app }) {
    if (!app.mixins) {
        app.mixins = []
    }
    app.mixins.push({
        mounted () {
            this.$breakpoint.$data.isMounted = true
        }
    })
}

Then you can call it like $breakpoint.is.smAndDown instead of $breakpoint.is('smAndDown')

@ScreamZ

This comment has been minimized.

@johnleider johnleider added this to the v2.2.x milestone Oct 11, 2019
@gotenxds
Copy link

Any progress or workaround for this ?
It's getting really hard to make stuff work right when you need to change the viewport for nuxt to realize it's in phone

YJdEsGfGfo

^
This happens on a real phone as well.

@douira
Copy link
Contributor

douira commented Dec 30, 2019

This is what I have come up with loosely based on the comment by pantunchy: https://gist.github.com/douira/6d3f99fa4546adee470637467931ed19
Since this creates a global watcher for every vuetify breakpoint this is probably not the most elegant solution. I'm also not sure if this is ok performance-wise but I can't really imagine something bad happening from looping through a hand full of properties.

@magyarb
Copy link

magyarb commented Feb 26, 2020

This is getting pushed back from v1.2.0 (mid 2018!) to v3.0.0. I think this is important, as everyone developing nuxt with vuetify will face this issue, breaking hydration completely.

@pantuchy 's solution works really well. I think it should be included by default in the create-nuxt-app when choosing vuetify. Also, it should be included in the docs.

Do you think it would be a good idea to submit this as a PR to create-nuxt-app? Or is another solution under development?

@douira
Copy link
Contributor

douira commented Feb 26, 2020

The breakpoints xsAndUp and xlAndDown don't exist in Vuetify and I've removed them from my updated version of the plugin. See this Vuetify issue about those two breakpoints: #6019

I had some difficulties with the other version of the plugin so I made this one. I don't know which one is preferable for performance and architecture quality.

How to use

Place this file in plugins/breakpoint.js. Register it with Nuxt.js in nuxt.config.js:

export default {
  plugins: ["~/plugins/breakpoint"]
}

and then you can use $breakpoint in your components with an expression like this.$breakpoint.smAndUp.

Workaround

See more info: https://gist.github.com/douira/6d3f99fa4546adee470637467931ed19

import Vue from "vue"

//the properties of breakpoint that we want to mirror
const defaults = {
  xs: true,
  xsOnly: true,
  sm: false,
  smOnly: true,
  smAndDown: true,
  smAndUp: false,
  md: false,
  mdOnly: false,
  mdAndDown: true,
  mdAndUp: false,
  lg: false,
  lgOnly: false,
  lgAndDown: true,
  lgAndUp: false,
  xl: false,
  xlOnly: false
}

//create a property on the prototype of all instances that holds the breakpoint state
Vue.prototype.$breakpoint = new Vue({
  data: () => ({ ...defaults })
})

export default async function({ app }) {
  //init mixins and the watchers if they don't exist yet
  app.mixins = app.mixins || []
  app.watch = app.watch || {}

  //create a watcher for each breakpoint
  for (const prop in defaults) {
    //the watcher sets the breakpoint prop to cause an update
    app.watch[`$vuetify.breakpoint.${prop}`] = function(value) {
      //update our mirrored value properly
      this.$breakpoint[prop] = value
    }
  }

  //add a mixin that does the client prop setting
  app.mixins.push({
    //here is the magic, if we set the state with the correct value on client init it works
    mounted() {
      //for all props that we are processing
      for (const prop in defaults) {
        //set the initial value from vuetify
        this.$breakpoint[prop] = this.$vuetify.breakpoint[prop]
      }
    }
  })
}

@rwam
Copy link
Contributor

rwam commented May 4, 2020

Sorry @douira, your solution don't work 😢 If I change the viewport the app crashes with the following error:

TypeError: Cannot read property '$breakpoint' of undefined
    at Vue.app.watch.<computed> (breakpoint.js?7a54:92)
    at Watcher.run (vue.runtime.esm.js?2b0e:4568)
    at flushSchedulerQueue (vue.runtime.esm.js?2b0e:4310)
    at Array.eval (vue.runtime.esm.js?2b0e:1980)
    at flushCallbacks (vue.runtime.esm.js?2b0e:1906)

Tried the solution provided by @pantuchy and this works. But it's just a workaround.

@douira
Copy link
Contributor

douira commented May 4, 2020

Both are workarounds and work differently. Neither is a solution since they both do a similar thing; creating a reactive breakpoint object that also works in SSR even though Vuetify currently has none.

I can't figure out what went wrong in your project since this stacktrace doesn't really say anything. Are you sure you're not using .is? You need to use it with this.$breakpoint.smAndUp. If you show me more of the code using $breakpoint, I might be able to troubleshoot. If the other version works for you that's just fine as well though.

@ghost
Copy link

ghost commented Jun 22, 2020

If someone is interested in a workaround for a vue-ts class style component.
It inspired by the solution from @KaelWD and works like a charm.

<script lang="ts">
import { Vue, Component } from 'vue-property-decorator'
import { Breakpoint } from 'vuetify/types/services/breakpoint'

@Component({
})
export default class ExampleComponent extends Vue {
  breakpoint: Partial<Breakpoint> = {}

  mounted () {
    // assign vuetify breakpoints after mount, because there is no screensize available in ssr
    this.breakpoint = this.$vuetify.breakpoint
  }
}
</script>

@thepasterover
Copy link

Is this bug fixed? Because I keep getting the breakpoint value as xs even though my screen is lg. Thank you

@johnleider
Copy link
Member

We kindly ask users to not comment on closed/resolved issues. If you believe that this issue has not been correctly resolved, please create a new issue showing the regression.

If you have any additional questions, please reach out to us in our Discord community.

@vuetifyjs vuetifyjs locked as resolved and limited conversation to collaborators Apr 8, 2021
@KaelWD
Copy link
Member

KaelWD commented Apr 10, 2021

Yes, the first render will always be XS.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
framework Issues and Feature Requests that have needs framework-wide. Service: Breakpoint SSR SSR T: bug Functionality that does not work as intended/expected
Projects
None yet
Development

Successfully merging a pull request may close this issue.