JAVASCRIPT

Build a Simple State Management Solution with Vue 3 Composable and Provide/Inject

Create a lightweight, modular state management pattern in Vue 3 using a Composable with `provide` and `inject` for global access without prop drilling.

// composables/useCounterStore.js
import { ref, computed, provide, inject } from 'vue';

// A unique symbol for the injection key prevents collisions
const COUNTER_STORE_KEY = Symbol('counter-store');

/**
 * Creates and provides a counter store.
 * Call this once at a high level in your component tree (e.g., App.vue).
 */
export function provideCounterStore() {
  const count = ref(0);

  const increment = () => { count.value++; };
  const decrement = () => { count.value--; };

  const doubleCount = computed(() => count.value * 2);

  // Provide the store's state and actions
  provide(COUNTER_STORE_KEY, {
    count,
    doubleCount,
    increment,
    decrement
  });
}

/**
 * Injects and returns the counter store.
 * Call this in any descendant component to access the shared state and actions.
 */
export function useCounterStore() {
  const store = inject(COUNTER_STORE_KEY);
  if (!store) {
    throw new Error('useCounterStore must be used within a component tree where provideCounterStore has been called.');
  }
  return store;
}

// App.vue (Example where the store is provided)
<template>
  <div id="app-root" style="padding: 20px;">
    <h1>Vue 3 Custom Store Example</h1>
    <p>App-level Count: {{ count }} | Double: {{ doubleCount }}</p>
    <button @click="increment">Increment from App</button>
    <button @click="decrement" style="margin-left: 10px;">Decrement from App</button>

    <hr style="margin: 20px 0;">

    <ChildComponent />
    <AnotherChildComponent />
  </div>
</template>

<script setup>
import { provideCounterStore, useCounterStore } from './composables/useCounterStore.js';
import ChildComponent from './components/ChildComponent.vue';
import AnotherChildComponent from './components/AnotherChildComponent.vue';

// Provide the store at the root level of your application
provideCounterStore();

// You can also use the store in the same component where it's provided
const { count, doubleCount, increment, decrement } = useCounterStore();
</script>

<style>
body {
  font-family: Arial, sans-serif;
  margin: 0;
  background-color: #f4f4f4;
}
button {
  padding: 8px 15px;
  border: none;
  border-radius: 5px;
  background-color: #007bff;
  color: white;
  cursor: pointer;
  transition: background-color 0.2s;
}
button:hover {
  background-color: #0056b3;
}
</style>

// components/ChildComponent.vue (Example of a child component consuming the store)
<template>
  <div class="component-box">
    <h3>Child Component</h3>
    <p>Child-level Count: {{ count }} | Double: {{ doubleCount }}</p>
    <button @click="increment">Increment from Child</button>
  </div>
</template>

<script setup>
import { useCounterStore } from '../composables/useCounterStore.js';

const { count, doubleCount, increment } = useCounterStore();
</script>

<style scoped>
.component-box {
  border: 1px solid #ccc;
  padding: 15px;
  margin-bottom: 15px;
  background-color: white;
  border-radius: 8px;
}
</style>

// components/AnotherChildComponent.vue (Another child component consuming the store)
<template>
  <div class="component-box">
    <h3>Another Child Component</h3>
    <p>Count here: {{ count }}</p>
    <button @click="decrement">Decrement from Another Child</button>
  </div>
</template>

<script setup>
import { useCounterStore } from '../composables/useCounterStore.js';

const { count, decrement } = useCounterStore();
</script>

<style scoped>
.component-box {
  border: 1px solid #ccc;
  padding: 15px;
  margin-bottom: 15px;
  background-color: white;
  border-radius: 8px;
}
</style>
How it works: This snippet demonstrates how to implement a simple, modular state management solution in Vue 3 using a Composition API composable in conjunction with `provide` and `inject`. The `useCounterStore` composable encapsulates reactive state (`count`), computed properties (`doubleCount`), and actions (`increment`, `decrement`). By calling `provideCounterStore` at a high level in the component tree (e.g., `App.vue`), the store's state and functions are made available to any descendant component without the need for prop drilling. Descendant components simply call `useCounterStore` to `inject` and utilize this shared state, creating a lightweight, yet effective, global state pattern.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs