When to use state management libraries

There are so many ways to manage data in the frontend. In the Vue ecosystem we have a great library for this called Pinia. We also have great functions like ref and reactive. So where do state management tools like ref and reactive need to be used and when do you reach for Pinia?

First, let's say we have a simple Vue component.

<template>
  {{ count }}

  <button @click="count++">Inc</button>
  <button @click="count--">Dec</button>
</template>
<script setup lang="ts">
  import {ref} from "vue";

  const count = ref<number>(0);
</script>

This is perfectly fine. We have some local state that we manage right here in the component. The ref will update the view whenever you increase or decrease the count. However, what do we do if we have to show that count in a nested component? Well, we could "prop drill" the data down into the child.

<template>
  {{ count }}

  <button @click="count++">Inc</button>
  <button @click="count--">Dec</button>
  <Child :count_for_child="count" />
</template>
<script setup lang="ts">
  import {ref} from "vue";

  const count = ref<number>(0);
</script>

Okay, that works. However, you now need to make sure you maintain reactivity down into the child. If the child component can also update the state of the count you then need to emit that count back up to the parent. What if the child component that we want to display/update the state from is 3 or 4 components deeply nested? Then you have to maintain that reactivity up and down all of those levels. This can quickly become a pain to manage. This is where Pinia comes in!

Pinia manages the state of the count globally. So you keep your state inside of a Pinia store and access it from anywhere in your application.

Taken straight from the Pinia website.

// stores/counter.js
import { defineStore } from "pinia";

export const useCounterStore = defineStore("counter", {
  state: () => {
    return { count: 0 };
  },
  // could also be defined as
  // state: () => ({ count: 0 })
  actions: {
    increment() {
      this.count++;
    },

    decrement() {
      this.count--;
    },

    get() {
      this.count;
    }
  }
});

You can now use this inside of any component you want by importing the store.

<template>
  {{ counter.$state.count }}

  <button @click="counter.increment()">Inc</button>
  <button @click="counter.decrement()">Dec</button>
  <Child />
</template>
<script setup lang="ts">
  import { useCounterStore } from "./stores/counter"

  const counter = useCounterStore();
</script>

Want to use this 5 nested children deep? Just use the store and access the same methods and state that all other components use. This will be kept perfectly in sync and is accessible and able to be updated anywhere!

You may be asking, "Do I need Pinia for this? What happened to ref"? No, you don't need Pinia strictly speaking. ref and reactive, among other Vue state management functions are quite powerful and can be used in place of something like Pinia. In fact, Pinia is built on top of these same primitives. Pinia simply provides a very nice API to interact with and that may be worth it in larger projects. Pinia also has features for implementing plugins and various other nice-to-haves for larger projects.

I hope you enjoyed this article and it helps you understand when you might use a global state management tool like Pinia.

Published: Wed Mar 09 2022