Vue-DFS-Store: A Simple Store Wrapping Vue's Built-in Reactivity
Tags: vue, typescript, learning, showdev
Published: 2020-11-07 02:53:40

---
title: Vue-DFS-Store: A Simple Store Wrapping Vue's Built-in Reactivity
published: true
description: I created a Typescript Library for quickly providing and injecting stores into your Vue projects, and accessing these stores with accessors for state mutations and "getters."
tags: vue, typescript, learning, showdev
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/iw0vs4997j3uh9jvdynj.png
---

A few weeks ago while taking a dive back into the Vue ecosystem, I found an interesting article here on dev.to entitled **[You Might Not Need Vuex with Vue 3](https://dev.to/blacksonic/you-might-not-need-vuex-with-vue-3-52e4)**. Given the guidance and my desire to learn more about Vue's reactivity system, I wondered if I could create a simple store implementation built-on top of the reactivity system to easily initialize, provide, and inject stores into Vue components. 

I also recently played with the [Zustand](https://github.com/pmndrs/zustand), a state management solution mostly used with React, and liked the simplicity of creating actions with access to state via "set" and "get" methods. Of course, Vue's reactivity system works with mutable state, so there are some differences from React, but I wanted a similar API to what Zustand offers. 

I now present the product of my recent learning. 

## Introducing Vue-DFS-Store

Let's show how to use this with... a counter app because nothing under the sun is new. 

![Demo Application](https://dev-to-uploads.s3.amazonaws.com/i/7qrv8tdk5ja3jhsgspqy.gif)

### Create Store

We use the `createStore` function with the following properties provided in a configuration object.


```ts
import { createStore } from 'vue-dfs-store';

type CounterState = {
  count: number;
};

type CounterAccessors = {
  incCount: (val: number) => void;
  clearCount: () => void;
  multCount: (val: number) => number;
};

const counterStore = createStore<CounterState, CounterAccessors>({
  name: 'counterStore',
  initialState: {
    count: 0,
  },
  accessorsCreator: (mutate, get) => ({
    incCount: (val: number) => mutate(state => (state.count += val)),
    clearCount: () => mutate(state => (state.count = 0)),
    multCount: (val: number) => get().count * val,
  }),
  mutatorHook: state => console.log(state),
});

export default counterStore;
```

#### CreateStore Details

The configuration receives a `name`, an `initialState` object (a plain old object), an `accessorsCreator`, and a `mutatorHook`.

The `accessorsCreator` receives `mutate` and `get` functions, and returns an object of methods where we can get and/or mutate the state.

`get` provides access to a Vue *readonly* version of the reactive state. You can see how this is used in the `multCount` accessor below to compute a multiple of the current counter value.

The only access to the store's mutable state is provided by the `mutate` method. This is similar [Zustand's](https://github.com/pmndrs/zustand) `set` method. You can make as many mutations as you want inside of an accessor, and these accessor can also by async.

`mutatorHooks` is used to run some code after each mutation. In the future, I hope to add functionality to create multiple stores which share a common mutatorHook (as well as functionality for executing code both before committing the mutation and after committing the mutation). This can be used for storing state history down the road. 

You may provide explicit types for the state and accessors (as shown above) to `createState`, or you may rely on inferred types from the configuration.

### Created Store Properties

The createStore function returns the following, where the generic parameters T and U are the typings for the State and the Accessors.

```ts
// Store is returned by createStore()
export type Store<T extends State, U extends Accessors> = {
  readonly name: string;
  storeAPI: StoreAPI<T, U>;
  install: (app: App) => void; // makes Store implement Plugin from vue
  readonly storeKey: symbol;
  provider: () => void;
};
```

The main property you will be using in our components is the `storeAPI`, and this is what will be returned from the useStore function, described below. 

The StoreAPI is typed as follows. You can see it exposes a ReadOnly version of the reactive state (both readonly provided by Vue, and readonly provided by Typescript).

```ts
export type StoreAPI<T extends State, U extends Accessors> = {
  readonly state: ReadonlyState<ReactiveState<T>>;
  accessors: U;
};
```

### Provide Store

Observe that the object returned by `createStore`, has an `install` method and a `provider`. 

You can pass the store directly to `app.use()` as follows to provide the store at the root of your application. 

```ts
import { createApp } from 'vue';
import App from './App.vue';
import counterStore from './store/counter';

createApp(App)
  .use(counterStore)
  .mount('#app');
```

You can also import and call `provider` from the store inside of any setup method inside of any Vue components down your tree structure.

### Use Store

Import the `useStore` function and pass it the store we just created to get access to the readonly state and accessors. Look at how we can use an accessor method inside of a `computed` to recalculate the `multCount` method when it's reactive dependency, `multiplier` is updated.

If you need to "spread" to reactive state object returned from useStore, use [toRefs](https://v3.vuejs.org/api/refs-api.html#torefs).

```vue
<script lang="ts">
import { defineComponent, ref, computed, toRefs } from 'vue';
import { useStore } from 'vue-dfs-store';
import counterStore from '../store/counter';

export default defineComponent({
  name: 'Counter',
  setup() {
    const multiplier = ref(0);
    const { state, accessors } = useStore(counterStore);

    const { count } = toRefs(state);

    const multipliedCount = computed(() =>
      accessors.multCount(multiplier.value)
    );

    return {
      state,
      count,
      incCount: accessors.incCount,
      clearCount: accessors.clearCount,
      multiplier,
      multipliedCount,
    };
  },
});
</script>
```

### Learn More!

Check out my [Github repo](https://github.com/JacobSNGoodwin/vue-dfs-store) for source code, documentation, and how to run the demo application.

If you would like me to go over the source code, I'll write up another article. Just let me know in the comments!

## Why Not Just Use Vuex?

Noooo.... by all means, use it!

While [Vuex](https://next.vuex.vuejs.org/#what-is-a-state-management-pattern) is the obvious option for state management in Vue, I did find typing the stores in the upcoming v4 to be a little difficult and sparsely documented (or at least sparsely "exampled"). Check out Vuex's [Typescript documentation](https://next.vuex.vuejs.org/guide/typescript-support.html#typing-usestore-composition-function) for what it takes to inject a store with a single "count" property into your application. Perhaps I just need a little more practice, or perhaps the Vuex team just needs some time to update the documentation and for bloggers to provide better examples. 

Either way, I by no means hope to, nor think I can, replace Vuex. As I wrote, I merely wanted to learn more about Vue's reactivity system and Composition API. Hell, there could be some major flaws in my thinking. If so, please let me know.

But if you do find this useful, let me know, and maybe I'll work on a build pipeline for this project so making improvements can go smoothly!

## An Issue I could use your help with

I wanted the `storeAPI` to directly provide access to the spread, `toRefs`, version of the state when calling `useStore()`. However I kept getting the following Typescript error when I wrapped the readonly state in ToRefs: 

> error TS2590: Build:Expression produces a union type that is too complex to represent.

I did find some issues addressing this, but could not determine how to simplify my Typescript to fix this. My suspicion if that the complexity of `DeepReadonly`from vue's reactivity typing, combined with `ToRefs` and this library's types was just creating too complex of a type. I could use a ToRefs writable object, or DeepReadonly object only.

Below is the type used when creating a DeepReadonly (with `readonly()`) in [Vue3](https://github.com/vuejs/vue-next/blob/master/packages/reactivity/src/reactive.ts).

```ts
type Primitive = string | number | boolean | bigint | symbol | undefined | null
type Builtin = Primitive | Function | Date | Error | RegExp
export type DeepReadonly<T> = T extends Builtin
  ? T
  : T extends Map<infer K, infer V>
    ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
    : T extends ReadonlyMap<infer K, infer V>
      ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>>
      : T extends WeakMap<infer K, infer V>
        ? WeakMap<DeepReadonly<K>, DeepReadonly<V>>
        : T extends Set<infer U>
          ? ReadonlySet<DeepReadonly<U>>
          : T extends ReadonlySet<infer U>
            ? ReadonlySet<DeepReadonly<U>>
            : T extends WeakSet<infer U>
              ? WeakSet<DeepReadonly<U>>
              : T extends Promise<infer U>
                ? Promise<DeepReadonly<U>>
                : T extends {}
                  ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
                  : Readonly<T>
```