Using an external store in Vue 3

(Updated January 30, 2023)

In this article, we will see how to use an external store in Vue 3 using the composition API. Also, will use the publish-subscribe pattern to achieve this. I'm not a Vue wizard, so if you see something wrong, please let me know .

As I said in the Implement the publish-subscribe pattern in TypeScript article, I'm working on a refector of a personal project. To manage the state, I've decided to use the publish-subscribe pattern.

The store

// subscriber.ts

export type ISubscribe<T> = {
  getState: () => T;
  setState: (value: Partial<T>) => void;
  subscribe: (callback: () => void) => () => void;
};

export const subscriber = <T>(initialValue: T): ISubscribe<T> => {
  const listeners = new Set<() => void>();
  let value = initialValue;

  const getState = () => value;

  const setState = (newValue: Partial<T>) => {
    value = { ...value, ...newValue };
    listeners.forEach((listener) => listener());
  };

  const subscribe = (callback: () => void) => {
    listeners.add(callback);
    // Return a function to unsubscribe.
    return () => listeners.delete(callback);
  };

  return {
    getState,
    setState,
    subscribe,
  };
};

The Vue 3 hook

// useSubscriber.ts

import { shallowRef, watch, onUnmounted } from "vue";
import { subscriber } from ".subscriber";

const initialState = {
  steps: [],
  currentStep: 0,
};

export const useSubscriber = () => {
  const subs = subscriber(initialState);

  // Create a shallowRef to store the state.
  const state = shallowRef(subs.getState());

  // Function to update our state.
  const updater = (state) => {
    subs.setState(state);
  };

  // Subscribe to the store and update the state.
  const unsubscribe = subs.subscribe(() => {
    state.value = subs.getState();
  });

  // I don't know if this is necessary.
  onUnmounted(() => {
    unsubscribe();
  });

  return [state, updater];
};

The component

// App.vue

<script setup>
import { useSubscriber } from "./useSubscriber.ts";

const [steppers, updater] = useSubscriber();

function nextStep() {
  const currentStep = steppers.value.currentStep;
  updater({ currentStep: currentStep + 1 });
}
</script>

<template>
  <p>current step: {{ steppers.currentStep }}</p>
  <button @click="nextStep">next step</button>
</template>

Here an example on Vue playground

References