Connection Indicator Using Vue & Vuex

Keagan Chisnall
5 min readMar 18, 2019

When making a Progressive Web App (PWA) it’s super-important to handle a no-internet event/state. If no internet changes any functionality (and I imagine it would) you should really notify the user. Yesterday I decided to upgrade my boring popup to a little connection light. It only took 5 minutes, but after getting the warm and fuzzies about the simplicity of doing so in Vue, I thought I’d share it.

*** I‘m not including step-by-step project setup instructions, but for this example I just created a new project using Vue CLI (Babel and Vuex plugins).

Step 1: Borrow some nice HTML & CSS code

My first step was looking for inspiration on codepen.io. I found F. Stephen Kirschbaum’s CSS LED Lights and cut it way down (in codepen).

Step 2: Make a new component

Next, I made a new ConnectionLight.vue component in my project:

// src/components/ConnectionLight.vue<template>
<div class="led-box">
<div class="led-light"
:class="connected ? 'led-green' : 'led-red'"></div>
<p>{{ connected ? '' : 'Not ' }}Connected</p>
</div>
</template>
<script>
export default {
computed: {
connected() {
return false
}
}
}
</script>
<style scoped>
.led-box { height: 30px; width: 100%; margin: 50px 0; }
.led-box p { font-size: 12px; text-align: center; margin: 1em; }
.led-light { margin: 0 auto; width: 24px; height: 24px; border-radius: 50%; }
.led-red { background-color: #f00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #441313 0 -1px 9px, rgba(255, 0, 0, 0.5) 0 2px 12px; }
.led-green { background-color: #abff00; box-shadow: rgba(0, 0, 0, 0.2) 0 -1px 7px 1px, inset #304701 0 -1px 9px, #89ff00 0 2px 12px; }
</style>

In the <script> block, I just have one variable (‘connected’). For now it’s returning a static boolean, but soon it’ll be dynamic (which is why I make it a computed property instead of a data property).

The <template> block is basically the HTML I modified in codepen. The big difference is, you’ll see some of Vue’s magic in the form of two ternary statements that depend on the connection state. The first adds an additional class, and the second changes the displayed text underneath.

Finally, the <style> block is ‘scoped’ which prevents class names from clashing with other CSS classes, and (again) is mostly just the CSS I modified in codepen.

Step 3: Add the new component

Now in App.vue (or wherever you want this component), I imported and added the component as follows:

// src/App.vue<template>
<div id="app">
...
<connection-light></connection-light>
...
</div>
</template>
<script>
import ConnectionLight from './components/ConnectionLight.vue'

export default {
components: {
ConnectionLight
}
}
</script>
<style>...</style>

Step 4: Test the component

Running all that should display a nice red light wherever you placed it:

To test it, changing the return value of the ‘connected’ property in the ConnectionLight component to true produces:

Step 5: Setup Vue’s Store (Vuex)

I’m not going into what Vuex is, or why you’d use it (try here if you don’t know), but this is a great place to use it. That’s because our connected state is likely to be referenced in many parts of the app (not just our component), so having one place to update and retrieve that state from means simplicity and fewer places bugs can hide.

Now I will say, Vuex has always stuck out for me as one of Vue’s very few seemingly convoluted ways of doing things, but that’s for another day. If you haven’t seen it before it can look like a lot for just one reactive variable called ‘connected’ but when you understand the different bits, this is very simple.

// src/store.jsimport Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)export default new Vuex.Store({
state: {
connected: navigator.onLine,
},
getters: {
connected: state => state.connected,
},
mutations: {
'SET_CONNECTED' (state, payload) {
state.connected = payload
},
},
actions: {
setConnected ({ commit }, payload) {
commit('SET_CONNECTED', payload)
},
}
})

The only thing which may need some explanation is the initial state given to connected (navigator.onLine). This is a browser window variable on what the current connection state is.

Step 6: Reference the store variable in the component

So going back to our component code, I can now link up the computed property ‘connected’ to its Vuex counterpart:

// src/components/ConnectionLight.vue...
<script>
export default {
computed: {
connected() {
return this.$store.getters['connected']
}
}
}
</script>
...

And now running it all again produces a green light (as I am in fact connected).

Step 7: Listen for connection changes

Finally, let’s get live changes by listening to window events. For simplicity, I’m adding them during the creation of our root Vue instance in the main.js file:

// src/main.jsimport Vue from 'vue'
import App from './App.vue'
import store from './store'
new Vue({
store,
render: h => h(App),
created () {
window.addEventListener('offline', () => {
store.dispatch('setConnected', false)
})
window.addEventListener('online', () => {
store.dispatch('setConnected', true)
})
}
}).$mount('#app')

Now the two listeners have references to our Vuex store and can call the setConnected action as needed.

Final check

So now when you run the app, the light should constantly reflect your connection state. Most browsers have a development function that lets you make a tab offline, which will let you play around with it. Here’s Chrome’s:

Should be working now, but I’m always here for troubleshooting and clarification. For my own (real) needs I had a third state testing for Firebase database connectivity, so let me know if you want to see that! Thanks for reading and give me a clap or 10 if you learned something or just like supporting writers 😊

📝 Read this story later in Journal.

👩‍💻 Wake up every Sunday morning to the week’s most noteworthy stories in Tech waiting in your inbox. Read the Noteworthy in Tech newsletter.

--

--

Keagan Chisnall

I have a background in process engineering, management, and consulting; but my passion is making systems and processes more efficient. Visit me: chisnall.io