Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

VueJS Showing and Hiding Messages

I have a basic CLI structure environment created. I have a component to display messages/alerts Ie: Login Failed etc…

Since this component is going to be reused throughout the entire app, I figured to import it to the root App.vue file and have it handled there. It is working…sort of.

It displays the messages/alerts fine, but I would like for it to hide/disappear/evaporate after a set amount of seconds. Alternatively Click a button and it hides/disappears/evaporates - I have this shown in the Alerts Component example below. I could not get the hide after a certain pre-defined time at all, and the click and hide works, but to un-hide creates an issue as well. I used setTimeout method for the auto hide after 5 seconds in the App.vue file and nothing would happen I think the method was firing before the Alert module was imported…i think.

Here’s my code so far…seems such a simple task but its been wrecking my brain for the past couple of hours:

App Component:

<template>
   <div id="app">
         <alert v-if="alert" v-bind:message="alert"></alert>
         <router-view></router-view>
   </div>
</template>

<script>
   import Alert from './components/frontend/alert'
   export default {
      name: 'App',
      data() {
         return {
            alert: ''
         }
      },
      updated: function() {
         if(this.$route.query.alert){
            this.alert = this.$route.query.alert;
            // This was for me to test the click even - PREFER AUTO HIDE AFTER A FEW SECONDS
           // This returns an error on form submit - see error below
            document.querySelector('.alert').style.display = 'block';
         }
      },
      components: {
         'alert': Alert
      }
   }
</script>

Here is the Alert Component:

<template>
   <div class="alert">
      <p class="text-brand m-20">
         <button class="btn btn-small btn-brand" v-on:click="hideAlert()">Close</button>
         {{message}}
      </p>
   </div>
</template>

<script>
   export default {
      name: 'alert',
      props: ['message'],
      data () {
         return {

         }
      },
      methods: {
         hideAlert() {
            // This was for me to test the click even - PREFER AUTO HIDE AFTER A FEW SECONDS
            document.querySelector('.alert').style.display = 'none';
         }
      }
   }
</script>

Error using the click to hide - coming from the App.vue file:

[Vue warn]: Error in updated hook: "TypeError: Cannot read property 'style' of null"

found in

---> <App> at src/App.vue
       <Root>

How can I have the Alert component hide after, let’s say 5 seconds, from the App root component? This would be my preferred method, otherwise what can I do to have the click and hide working?

Thanks so much!

like image 590
Sergio Avatar asked Dec 24 '22 10:12

Sergio


2 Answers

document.querySelector('.alert').style.display = 'none';

Don't do this. You should not be manipulating the DOM in methods, only in prescribed places like directives and lifecycle hooks. Outside of them, Vue expects to have control of the DOM.

You can control inline styles using your viewmodel. You can also do conditional rendering with v-if. The Vue approach is for you to manipulate your model and have Vue make the DOM reflect it.

I've adapted your code into a runnable snippet below. Since you put the hideAlert method in the component, I put the associated v-if there. The test is whether message (the prop) has a value, so closing is a matter of having the parent clear the message. This is a standard communication function handled with the .sync modifier.

The close button calls the hideAlert method, and I also put a watcher in so that any time a new message is set, it waits 5 seconds and calls hideAlert.

The Alert component is self-contained; it does not matter how its prop gets its value, whether the parent gets it from a router component, for example, it only matters whether it has a value or not.

const Alert = {
  template: '#alert-template',
  props: ['message'],
  methods: {
    hideAlert() {
      // Tell the parent to clear the message
      this.$emit('update:message', '');
    }
  },
  watch: {
    message(newValue) {
      // Close after 5 seconds
      if (newValue) {
        setTimeout(this.hideAlert, 5000);
      }
    }
  }
};

new Vue({
  el: '#app',
  data() {
    return {
      alert: ''
    }
  },
  components: {
    'alert': Alert
  },
  mounted() {
    // If alert has a value, it will display. If not, not.
    setTimeout(() => {
      this.alert = 'Now you have a message';
    }, 500);
  }
});
<script src="//unpkg.com/vue@latest/dist/vue.js"></script>
<div id="app">
  <alert v-bind:message.sync="alert"></alert>
</div>

<template id="alert-template">
   <div v-if="message" class="alert">
      <p class="text-brand m-20">
         <button class="btn btn-small btn-brand" v-on:click="hideAlert()">Close</button>
         {{message}}
      </p>
   </div>
</template>
like image 170
Roy J Avatar answered Jan 07 '23 07:01

Roy J


Firstly, it's important to understand the Vue and the v-if directive. v-if evaluates it's expression and if it evaluates to true, then Vue will render that element in the DOM, but if not, the element will not be included in the DOM.

This leads to the error message that you're seeing, since when there is no alert to show, there is no element in the DOM matching document.querySelector('.alert').

Additionally, it would be better if you have all hiding/showing code in the one component. For example, if you want it in the parent component, your hideAlert() method should be:

methods: {
    hideAlert() {
        this.alert = null
    }
}

And your alert button component would be:

<button class="btn btn-small btn-brand" v-on:click="$emit('hide')">Close</button>

And where you have the <alert> tag in the parent would become:

<alert v-if="alert" v-bind:message="alert" @hide="hideAlert"></alert>
like image 32
David A Avatar answered Jan 07 '23 08:01

David A