Building Reusable State Logic with Vue 3 Composables
Learn how to extract reactive state into Vue 3 composables to eliminate duplication, improve testability, and keep your components clean.
12 Dec 2025, 10:42 UTC

Problem: State Logic Scattered Across Components
When several components need the same counter, form validation, or API request state, copying the same ref() and methods leads to duplication and makes future changes error‑prone.
Thesis: Extract Reactive Logic into Composables
A composable is a plain JavaScript function that returns reactive primitives (ref or reactive) and any helper functions. By importing it where needed, you keep the template clean and centralize state updates.
Worked Example: A Reusable Counter Composable
- Create a new Vue 3 project (run in your terminal):
npm create vue@latest my-vue-app -- --typescriptRun the command in a directory where you have write permission; it scaffolds a Vite‑based Vue 3 project with TypeScript support. No elevated privileges are required.
- Add a composable file
src/composables/useCounter.ts:
import { ref } from 'vue'
export function useCounter(initial = 0) {
const count = ref(initial)
function increment() {
count.value += 1
}
return { count, increment }
}
This file defines a composable that takes an optional starting value and returns a mutable count ref and an increment function.
- Use the composable in a component, e.g.
src/components/CounterDemo.vue:
Count: {{ count }}
+1
Save the file and start the dev server:
npm run dev
Open http://localhost:5173 (default Vite port) and click the button; the number updates.
Verifying the Reactive State with Vue Devtools
- Install the Vue Devtools extension for your browser (Chrome/Firefox).
- Open the Devtools panel, select the
CounterDemocomponent. - In the State tab you will see a entry for
countwhose value changes as you click.
This confirms that the composable’s ref is properly tracked by Vue’s reactivity system.
Trade‑off and Limitation
While composables excel at sharing non‑trivial state, extracting every tiny UI toggle (e.g., a boolean for a dropdown) adds an extra indirection that can make the component harder to read at a glance. For such local flags, a plain const show = ref(false) inside setup() is often clearer.
Another caveat: in server‑side rendering (SSR) you must initialize refs inside setup() (or inside the composable called from setup()) to avoid leaking state between requests. If you create a ref at module level, its value persists across SSR renders and can cause stale data.
Actionable Closing
- Identify a piece of state that appears in two or more components.
- Move its
ref/reactiveand related helpers into a composable function. - Import the composable wherever needed and verify the updates in Vue Devtools.
- Keep component‑local UI toggles as plain refs to avoid unnecessary abstraction.
By following this pattern you keep your Vue 3 codebase DRY, easier to test, and fully compatible with TypeScript inference and Devtools inspection.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.