TLDR; Check out the code on Code Sandbox to learn how to make a snackbar that can be opened from any Vue component in your app!
In one of my Vue projects, I display a simple popup ("snackbar") explaining any errors that get thrown. I started to find myself adding a snackbar to every component that could possibly throw an error (such as from a network call). On top of that, I had to add data members controlling the visibility of the snackbar and the message it displayed.
This article explains how to create a single snackbar that you can then open from any component with any message you want. This eliminates any duplicate snackbar code across components. Let's get started!
Step 1: Create the event bus
To make a snackbar that can be opened across the entire app, we'll use a pattern called an event bus. An event bus is a global object that can receive and respond to events. Inside our Vue components, we can add a snackbar event to the bus and have it respond by opening a snackbar.
Lucky for us, we can just use a new Vue object to function as an event bus. It provides the handy $emit
and $on
functions to emit and respond to events.
// EventBus/index.js
import Vue from 'vue';
export const ACTIONS = {
SNACKBAR: 'snackbar',
};
const EventBus = new Vue();
export default EventBus;
That's it! For convenience, I also added an ACTIONS
object that makes it easy to constrain the actions that can be emitted on the bus.
Step 2: Create a function to open the snackbar
I created a folder called globalActions
with an index.js
file to contain functions that could be used in any component across my app. Let's put a function in there that will add the snackbar event to the event bus:
// globalActions/index.js
import EventBus, { ACTIONS } from '../EventBus/index';
export const showSnackbar = message => {
EventBus.$emit(ACTIONS.SNACKBAR, message);
};
Now let's create a component that uses this function to display a snackbar.
Step 3: Use the function in a component
I created a custom component called MyComponent
to use this snackbar function:
<!-- components/MyComponent.vue -->
<template>
<v-btn @click="openSnackbar">Show snackbar</v-btn>
</template>
<script>
import { showSnackbar } from '../globalActions';
export default {
methods: {
openSnackbar: () => {
showSnackbar('Hello from snackbar!');
},
},
};
</script>
This imports the showSnackbar
function and calls it with the message "Hello from snackbar!" when a button is pressed. Right now we won't see a snackbar because all that will happen is an event gets emitted on the event bus. Now let's tell the event bus that when it sees the ACTIONS.SNACKBAR
event, it should show a snackbar. We'll add this to our App.vue
file so that any component will be able to display a snackbar.
Step 4: Add a snackbar to App.vue
I'm using Vuetify in my project, so it's really easy to just pop in Vuetify's snackbar. However, you can easily accomplish the same goal with any other library or even your own custom snackbar. I just have one snackbar component in App.vue
and some data members to control its visibility and the message it displays:
<!-- App.vue -->
<template>
<div id="app">
<my-component />
<v-snackbar v-model="snackbar" timeout="2500"
>{{ snackbarMessage }}</v-snackbar
>
</div>
</template>
<script>
import EventBus, { ACTIONS } from './EventBus/index';
import MyComponent from './components/MyComponent.vue';
export default {
name: 'App',
components: {
MyComponent,
},
data: () => ({
snackbar: false,
snackbarMessage: '',
}),
mounted() {
EventBus.$on(ACTIONS.SNACKBAR, message => {
this.snackbarMessage = message;
this.snackbar = true;
});
},
};
</script>
When the component is mounted, we use the $on
function on our event bus to listen to the ACTIONS.SNACKBAR
. It updates the snackbarMessage
member and sets snackbar
to true. Now whenever we call the showSnackbar
function from a component, a snackbar will pop up with the passed-in message!
Check out the full code on Code Sandbox.
Conclusion
An event bus provides a super easy way to respond to events that could happen at any place in our app. You can also customize the snackbar as much as you want since the message it displays doesn't have to be the only argument you pass to the bus. For example, you could pass in options for the snackbar color, the duration it stays open, or a callback function to perform when the snackbar is clicked.
That's it for today, happy snackbar-ing!
Top comments (3)
Why you don't use vuex and some action for global error handler, whom shows snackbar message?
Yeah, I actually thought about doing it this way at first but decided I didn't really want my snackbar's state to be stored in the global Vuex store. Of course, this is a matter of personal preference, but I tend to only store information in my store that pertains to the app as a whole (e.g. information about the logged-in user, the cache for network requests, etc.). A snackbar is so temporary that I didn't quite feel justified in storing its state in the store. I couldn't see myself needing to access that information globally across other components.
This article only talks about passing a message to be displayed by the snackbar, but there are a lot of other options that you might want to pass in too. See this npm package for example. If I wanted to support options like those, then I definitely wouldn't want that information hanging around in the global store.
I should add that I definitely don't think you should default to event buses for solving your Vue problems. As you can imagine, it can get pretty messy if you have components adding events to the bus all over your app. I think most problems can be solved with props or Vuex. I've been working with Vue for two years and this snackbar case is literally the only time when I've used an event bus and thought it made more sense than other alternatives.
Thanks for reply.