Soonk
Soonk

Reputation: 352

vue/nuxt-How can I ensure the value from template is obtained first and passed to the child element?

I'm trying to get width value from Template and pass the value to child component in vue/nuxt.

However, it doesn't work out. I assume that is because width from template is obtained later stage in life cycle hook.

How could I ensure the width value is obtained first and the child element gets the information?

Parent

<template>
  <v-col cols="12" sm="12" md="4" ref="demandContainer">
    <div class="chart-container p-4 dark-background">
      <div class="font-weight-bold">Demand</div>
      <area-chart
        v-if="trendsData.length > 0"
        :data="trendsData"
        :baseWidth="width"
      >
      </area-chart>
    </div>
  </v-col>
</template>

<script>
export default {
  updated() {
    this.width = this.$refs.demandContainer.$el.clientWidth
  },
}
</script>

Child

<svg ref="svg-area" :width="baseWidth" :height="baseHeight">
</svg>

Error Message enter image description here

Upvotes: 1

Views: 496

Answers (2)

kissu
kissu

Reputation: 46774

Refs are available only once the component is mounted to the actual DOM.
In created, it is created somewhere but not yet appended to your page.

The issue as explained here is that you may have some issues to pass it to the child component since child's mounted lifecycle hook will happen before the parent's one.

I've tried some super hacky things

async mounted() {
  await this.$nextTick()
  this.$watch(
    () => {
      return this.$refs.test.$el.clientWidth
    },
    (val) => {
      console.log('nice', val)
    },
  )
},

It did not work because clientWidth would be available at an even later point (lifecycle-wise). So, the last solution is probably to use some even more ugly setTimeout.


Or you could fix the issue with CSS, it will be:

  • faster
  • less heavy on the webpage
  • more adapted
  • less error prone

Because SVG are meant to be super responsive and easy to work with.
Can you show us what you want to do with your SVG?

TLDR: don't use JavaScript to solve a responsive issue here. Having to work with parent + child + mounted lifecycles is not a good idea usually.

Upvotes: 0

hamid niakan
hamid niakan

Reputation: 2871

for the $refs to work you should make sure the template is fully rendered to the DOM and to do this you can have this code in the mounted hook:

mounted() {
  this.$nextTick(() => { this.width = this.$refs.demandContainer.$el.clientWidth; });
}

according to vue doc for $nextTick:

vm.$nextTick( [callback] ): Defer the callback to be executed after the next DOM update cycle. Use it immediately after you’ve changed some data to wait for the DOM update. This is the same as the global Vue.nextTick, except that the callback’s this context is automatically bound to the instance calling this method.

so with the help of $nextTick you can make sure the element is rendered to DOM to use the ref defined on the element.

Edit

actually it is $el thats undefined. this.$refs.demandContainer returns the element itself and to get the width you can just have this.$refs.demandContainer.clientWidth.

check the example below:

new Vue({
  el: '#app',
  vuetify: new Vuetify(),
  mounted() {
    console.log(this.$refs.demandContainer.clientWidth);
  },
})
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuetify.js"></script>

<div id="app">
  <v-app>
    <v-main>
      <v-container>
        <v-col cols="12" sm="12" md="4" ref="demandContainer">
          <div class="chart-container p-4 dark-background">
            <div class="font-weight-bold">Demand</div>
          </div>
        </v-col>
      </v-container>
    </v-main>
  </v-app>
</div>

Upvotes: 0

Related Questions