Vue · State

Vue 3 State Management Patterns

Vue 3 gives you a ladder of state options, and reaching straight for a global store skips most of it. Matching the pattern to the scope keeps an app maintainable.

John Kihiu12 min read

Vue 3 offers several ways to manage state, and the common mistake is jumping straight to a global store (Pinia) for everything. That over-centralises state and couples unrelated parts of the app. There is a ladder of options from most local to most global, and the skill is choosing the least-global one that fits the scope of the state in question.

Match the pattern to the scope

Scope of statePattern
One componentLocal ref / reactive
Parent to childProps down, events up
Deeply nested subtreeprovide / inject
Reusable stateful logicA composable
Truly global / cross-appPinia store

Start at the top and only move down as the state's reach genuinely grows. Most state is local; a lot of the rest is parent-child. Only a small fraction is genuinely global, and that is what Pinia is for.

Props and events for parent-child

For state a parent shares with its children, props down and events up is the idiomatic Vue pattern and usually all you need. It keeps data flow explicit and traceable — you can see exactly where state lives and how it changes. Do not reach for a store just to avoid passing a prop through one level; the explicitness is a feature, not a burden.

provide/inject for deep trees

When passing props through many intermediate layers becomes tedious (prop drilling), provide/inject lets an ancestor supply state to any descendant without threading it through every level. It suits things like a theme, a form context, or configuration used deep in a subtree. It is more implicit than props, so use it where the depth genuinely warrants it, not as a casual shortcut.

Composables for shared logic

Often what you want to share is not global state but reusable stateful logic — a composable is the answer. A useFetch or usePagination encapsulates the logic and each caller gets its own instance. For state shared across a few unrelated components without needing a full store, a composable holding module-level reactive state is a lighter option than Pinia.

Global state is the last resort, not the default

Every piece of state you put in a global store is state any part of the app can change, which is exactly what makes large apps hard to reason about. Keep state as local as its actual usage allows, climb the ladder only as reach demands, and reserve Pinia for state that is genuinely application-wide. Locality is what keeps an app maintainable.

Vue 3 state management is a ladder — local state, props and events, provide/inject, composables, and finally Pinia — and maintainability comes from choosing the least-global pattern that fits. Resist defaulting to a store; most state belongs closer to where it is used. Match the tool to the scope and the app stays easy to reason about as it grows.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.