Observing changes in VueJS using $watch and accessing DOM elements

How can I monitor changes in Vue $refs?

I'm trying to apply some logic to a child component that is nested within my current Vue instance. However, when trying to access '$refs.childcomponent' inside the 'ready' callback, it appears as 'undefined' during processing.

inside 'ready()'

this.$watch('$refs', function() {
    console.log("not firing");
}, { deep: true });

Outcome: Error message - Maximum call stack exceeded

'watch' property of the instance

watch: {
  '$refs': {
     handler: function() { console.log("hit"); },
     deep: true
  }
}

Outcome: No response.

Answer №1

Monitoring $watch on $refs.<name>.<data> is possible, but not on $refs.<name> itself or just $refs.

Check out this example on JSFiddle

const Counter = {
  data: () => ({
    i: 0
  }),
  template: `<fieldset>
    <p>Counter</p>
    <code>i = {{ i }}</code>
    <button @click="i += 1"> Add One </button>
  </fieldset>`
}

const App = {
  components: { Counter },
  mounted () {
    this.$watch(
        () => {
            return this.$refs.counter.i
        },
      (val) => {
        alert('App $watch $refs.counter.i: ' + val)
      }
    )
  },
  template: `<fieldset>
    <p>App</p>
    <counter ref="counter" />
  </fieldset>`
}

new Vue({
    el: '#app',
    render: h => h(App)
})

Answer №2

Unfortunately, $refs do not have reactive capabilities, so watch functionality will not be supported.

Answer №3

When using this code in a mounted state:

this.$watch(
        () => {
            return this.$refs.<name>.<data>
        },
      (val) => {
        alert('$watch $refs.<name>.<data>: ' + val)
      }
    )

Answer №4

There is a workaround for this particular situation. It's important to note that when assigning an array to a variable in JavaScript, it creates a reference to the original array rather than a copy. Since Vue's $refs are arrays, we can utilize this knowledge in the following way:

<template>
    <div>
            <ul v-if="showAvailable">
                 <li v-for="pet in allPets.available" :key="pet.id" ref="pets">
                      {{ pet.name }}
                 </li>
            </ul>
            <ul v-else>
                 <li v-for="pet in allPets.unavailable" :key="pet.id" ref="pets">
                      {{ pet.name }}
                 </li>
            </ul>
    </div>
</template>

<script>

export default {
    props: ['allPets'],

    data() {
         showAvailable: true // will usually change dynamically
         shownPets: null // initially set to null
    },

    mounted() {
         this.$set(this.$data, 'shownPets', this.$refs.pets);    
    },

    watch: {
         shownPets: {
                         handler(newVal, oldVal){
                              // Perform actions when there are DOM changes
                         },
                         deep: true
                    }
    }
}
</script>

This method allows us to assign our data shownPets to the pets stored in the $ref after the component has been mounted. The reference will reflect different elements depending on whether showAvailable is true or false, enabling us to monitor changes to $ref or the DOM effectively.

Answer №5

To achieve this, you can define the property in $refs during the created() lifecycle hook or beforeCreate() if the backing store in data is not required. Here is an example:

const vm = new Vue({
    el: "#app",
  data() {
    return {
        mode: 0,
        refTest: undefined
    };
  },
  created() {
    Object.defineProperty(this.$refs, 'test', {
      get: function () {
        return this.refTest;
      }.bind(this),
      set: function (newValue) {
        console.log(`set - Setting test refs to an array? ${Array.isArray(newValue)}`);
        this.refTest = newValue;
      }.bind(this),
      enumerable: true
    });
  },
  watch: {
    refTest: {
      handler(newValue, oldValue) {
        console.log(`watch - array? ${newValue ? Array.isArray(newValue) : undefined}`);
      },
      deep: false,
      immediate: true
    }
  },
  methods: {
    toggle() {
        this.mode = (this.mode === 1) ? 2 : 1
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <button @click="toggle">Toggle</button>
  <ul v-if="mode === 1">
    <li ref="test">Single Ref</li>
  </ul>
  <ul v-else-if="mode === 2">
    <li ref="test" v-for="s in ['Multiple', 'Refs']" :key="s">{{ s }}</li>
  </ul>
  <div v-else>No Refs</div>
</div>

This method proved effective for maintaining the reference even when it's not initially connected with the component.

The use of defineProperty is necessary because a change in the reference could lead to the watcher losing track of it.

If you prefer handling actions within defineProperty directly, you can eliminate the watcher. However, a backing store is always needed, albeit not necessarily in the data object.

Answer №6

Utilizing the MutationObserver in the following solution:

Within the mounted hook, include :

const config = {
      attributes: true,
      childList: true,
      subtree: true
    };
 // this will be triggered for any change in your element
    this.observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (mutation) {
          this.infoHeight = this.$refs.info.clientHeight + 'px'
          console.log(" changed ", this.$refs.info.clientHeight)
        }
      });
    });
//observe the referenced element
    this.observer.observe(this.$refs.info, config);

Complete illustration

Vue.config.devtools = false;
Vue.config.productionTip = false;

new Vue({
  el: '#app',
  data() {
    return {
      infoHeight: 0,
      observer: null,
      img: "https://images.ctfassets.net/hrltx12pl8hq/6TOyJZTDnuutGpSMYcFlfZ/4dfab047c1d94bbefb0f9325c54e08a2/01-nature_668593321.jpg?fit=fill&w=480&h=270"
    }
  },
  mounted() {
    const config = {
      attributes: true,
      childList: true,
      subtree: true
    };
    this.observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (mutation) {
          this.infoHeight = this.$refs.info.clientHeight + 'px'
          console.log(" changed ", this.$refs.info.clientHeight)
        }
      });
    });
    this.observer.observe(this.$refs.info, config);
  },

 beforeDestroy(){
   this.observer.disconnect()
  },
  methods: {
    changeImg() {
      this.img = "https://i.pinimg.com/originals/a7/3d/6e/a73d6e4ac85c6a822841e449b24c78e1.jpg"
    }
  }
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>

<div id="app" class="container">
  <p>{{infoHeight}}</p>
  <button class="btn btn-primary" @click="changeImg">Change image</button>
  <div ref="info">
    <img :src="img" alt="image" />
  </div>
</div>

Answer №7

Experience with Vue 3: a shallow $watch can be applied to $refs within the mounted() lifecycle hook.

mounted () {
  this.$watch(
    '$refs',
    this.$refs.childComponent?.whatever(),
    { immediate: true }
  )
}

It is crucial to utilize the optional chaining operator (?.) when accessing $refs.childComponent as it may initially be undefined. Once it is added to $refs, the watcher will trigger again.

Answer №8

If you happen to stumble upon this question looking for information on watching composition refs along with template refs, here's a helpful guide:

import { ref, watch } from 'vue'

const myRef = ref('hello')

watch(() => myRef.value, () => { 
  console.log('Watching', myRef.value)
  // Additional actions can be performed here
})

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

During the installation of a package, npm encountered a require stack error with the code MODULE_NOT_FOUND

Whenever I attempt to install something using the npm install command, it throws an error saying "require stack" and "code MODULE_NOT_FOUND" C:\Users\dell>npm audit fix node:internal/modules/cjs/loader:1075 const err = new Error(message); ...

"Is it possible to selectively load only a few images on a website that contains numerous

My website displays numerous images hosted on a server. Each page contains a maximum of 100 images, each within its own div element. At any given moment, only one div is visible due to the CSS "display" property, while the others are hidden using display:n ...

Integrating *.vue file compatibility into the Shopware 6 Storefront (Encountering issue with 'vue-loader' not being resolved)

I am looking to integrate a mini Vue app into the Shopware 6 storefront within the TWIG ecosystem. This is what I have accomplished so far: MyPluginTheme\src\Resources\app\storefront\build\webpack.config.js const { join, reso ...

Navigating with buttons in React JS

In my material-ui, I have a button set up like this: <Button style={green} raised="true" label="Continue to create group"}> CREATE NEW GROUP </Button> I am looking to make it so that when the button is clicked, it will take me ...

Can multiple functions be included in a component in Angular JS?

I have an AngularJS component and I want to add a new function like onclick(). Can this be done within the existing component or do I need to create a new one? app.component('phoneDetail', { templateUrl: 'new.html', controll ...

Unusual Encounters in Laravel: Navigating with React Router and the Back Button

I have nearly completed building a weather application with Laravel and have decided to integrate the front end using React/Redux/React-Router while utilizing Laravel for API calls. The only aspect that I have chosen to keep unchanged is my customized Lara ...

Utilizing Jquery's .load function will temporarily deactivate all other functions

Season's Greetings everyone! I have a unique personal messaging system on my website that utilizes jQuery for message display and manipulation. Let's delve into the specific file that controls this functionality: <!-- Fetching and displaying ...

Execute Validation Function on Every TextField and Radio Button

I'm new to Javascript and struggling to make my function work for both radio buttons and text fields. Here is the HTML code for the form: <form action="sendmail.php" method="post" name="cascader" onsubmit="prepareEventHandlers()" id="cascader"&g ...

Check if all items in the array exist in Mongodb, then update them; if not, insert

In my database, I have a collection of tags and I want to perform the following actions when a user enters an array of tags: If a tag in the array already exists, update its count If a tag in the array does not exist, insert it with a count of 0 Current ...

Strange yellow border appears when key is pressed in Quasar's QLayout

While working on a project with the quasar framework and electron.js, I encountered a strange bug where pressing a key causes the application frame to display a persistent yellow border. This border cannot be overridden, removed, or selected using devtools ...

What steps can be taken to address the issue of an undefined route parameter in React

In the code below, I have encountered an issue while testing a specific part. The route.params attribute seems to be incorrect: onMounted(async () => { try { const { tokenId } = route.params I was initially using this code to extract ...

The function myComponent.map does not exist

I am currently storing information that I am trying to pass to a component responsible for creating Tabs and TabPanel components (Material-UI) based on the provided data. Here is how the information is structured: let eventCard = [ { title: "T ...

Attempting to transmit checkbox data in jade

I am currently developing an app with Express, Node.js, and Mongo. I have encountered an issue while passing checkbox values to my database. My goal is to only pass the values of checked checkboxes back to the database. In my index.jade file, I attempted ...

Adjust the width of the table to scroll horizontally and size down to fit within the

My webpage is structured with a sidebar and content section, using flex display with the sidebar set to 0.15 flex and the content set to 0.85 flex. I want this page to be full width of the viewport. The issue arises when I try to incorporate a table into ...

Failed to update the innerHTML attribute for the anchor tag

I'm attempting to apply styles through DOM manipulation using Angular's renderer2. I have successfully updated styles for all HTML elements except for anchors. In the example below, I am trying to replace the text www.url.com with World within ...

What are the available choices for constructing HTML based on an ajax response?

Are there any alternatives or libraries available for constructing html from an ajax response? Currently, I am taking the json data received, creating the html as a string, and using a jQuery function to insert it into the DOM. However, I believe there mu ...

Accessing a Variable in one JavaScript File from Another JavaScript File

In the process of creating a basic game using only JavaScript and jQuery, I have split it into two separate pages. The first page contains all the rules and necessary information, while the second page is where the actual game takes place. My goal is to in ...

"Error encountered when making a request to Google API using Ember.js, response remains

Trying to fetch place suggestions from Google API using Ember js. Below is the code snippet for the service module: fetch(){ let url=`https://maps.googleapis.com/maps/api/place/autocomplete/json?input=IL&types=geocode&key=API_KEY` return Ember.RSV ...

Updating records in a MongoDB database using C# may seem challenging, but fear not as

This is the C# code I have written for updating a record in MongoDB: public static void updateSubmit(string id,string fname,string lname,string email,string password,string address) { string connectionString = "mongodb://10.10.32.125:27017"; Mo ...

Adjusting the shadow on the inserted image

Currently, I am using fabric.js to manipulate images that are added to my canvas. The issue I am facing is with the shadow around the image not being even. The code I have tried so far is displayed below, but you can view what I am aiming for by clicking h ...