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.