Reputation: 822
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!
Upvotes: 4
Views: 21784
Reputation: 43899
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>
Upvotes: 9
Reputation: 21
You Should hide it inside your alert component using the created life cycle to hide it like this:
`
created: {
setTimeout(() => this.message = [], 1000)
}
Upvotes: 0
Reputation: 449
Sorry for bumping this thread, but I think that is the best way to achieve this:
Example: Imagine we have the following data that is coming from server response
data() {
return {
serverMessages: [],
}
},
methods: {
onSubmit() {
this.$axios.$get('/myroute')
.then(res => {
this.serverMessages.push(res)
this.$emit('flush-message')
})
.catch(err => {
this.serverMessages.push(err.data)
this.$emit('flush-message')
});
},
mounted() {
let timer
this.$on('flush-message', message => {
clearTimeout(timer)
timer = setTimeout(() => {
this.serverMessages = []
}, 3000)
})
}
This way you can clear the timeout so it doesn't glitch if you have multiple messages for example. You can also integrate similar code to a separate controller.
Upvotes: 0
Reputation: 413
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>
Upvotes: 1