Global State Management in an Electron Application

State Management in an Electron Application

Bringing the VueJS tool Pinia into our Electron application to manage its internal state. This post assumes you have followed along with my previous post.

What is a Store?

A Store is an entity that manages the global state of your application. It allows you to read and write to, regardless of the component you are working within.

You may have heard of other packages that help manage a store, Vuex is the standard for Vue and Redux is the standard for React.

Installation

This assumes you are using Vue 3. \ If you are using Vue 2, please refer to their documentation.

Because we are hooking this up to an Electron application, which is just Javascript with extra steps, we can utilize our favorite package manager such as npm or yarn to install Pinia.

yarn add pinia
# or with npm
npm install pinia

Use the createPinia function to attach it to the VueJS app:

// renderer/main.js

import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'

createApp(App).use(createPinia()).mount('#app')

Pinia is now available throughout your Electron application where Vue is available.

Creating our first store

A store is defined using defineStore() and requires a unique name. In this case, I am using main as the unique id attached to this store.

// renderer/stores/main.js

import { defineStore } from 'pinia'

// useMainStore could be anything like useUser, useCart
// the first argument is a unique id of the store across your application
export const useMainStore = defineStore('main', {
  state: () => ({
    msg: 'Hello World!',
  }),
  getters: {
    message: state => state.msg,
  },
  actions: {},
})

Using our store within a VueJS component

Now that our store is made, we can import it into individual components, allowing our components to interact with the store.

import { useMainStore } from '@/stores/main'

Within the setup function, I call our store function and set the value within a const variable:

setup() {
  const main = useMainStore()
}

This allows me to interact with my store. For this simple example, I am displaying the message defined within the getter.

setup() {
  const main = useMainStore()

  return {
    message: computed(() => main.message),
}

The entire component can be seen below:

// renderer/components/Hello.vue

<template>
  <div id="hello">
    <img src="https://vuejs.org/images/logo.png" />
    <h1>{{ message }}</h1>
  </div>
</template>

<script>
import { computed, defineComponent } from 'vue'
import { useMainStore } from '@/stores/main'

export default defineComponent({
  setup() {
    const main = useMainStore()

    return {
      message: computed(() => main.message),
    }
  },
})
</script>

Getting HMR to work

While HMR is built into Pinia, it doesn't play well with Electron and requires a bit of additional configuration. Pinia provides additional documentation of this topic here.

HMR (Hot Module Replacement) allows you to edit your stores and see the changes within your app without having to reload the page or restart your server.

Here's what my main store looks like after updating it to allow for HMR:

import { defineStore, acceptHMRUpdate } from 'pinia'

export const useMainStore = defineStore('main', {
  state: () => ({
    msg: 'Hello World!',
  }),
  getters: {
    message: state => state.msg,
  },
  actions: {},
})

if (import.meta.hot) {
  import.meta.hot.accept(acceptHMRUpdate(useMainStore, import.meta.hot))
}

Passing acceptHMRUpdate your store (useMainStore in my case), it gives us HMR!

Hope you enjoyed!