Local Component State Patterns
Not every piece of state in an application needs to live in a global store. Most UI state — a modal's open/closed flag, a form's current input values, a list's sort order, a tab's active index — is naturally local to a single component or a small tree of related components, and keeping it local (rather than lifting it prematurely into Pinia) keeps components easier to reason about, test, and reuse. The Composition API gives several complementary patterns for structuring this local state cleanly.
Cricket analogy: Not every stat needs to go into the team's master record book — whether a fielder's cap is on straight is purely their own concern, not something the whole squad's ledger needs to track; likewise a modal's open/closed flag belongs local to its component, not Pinia.
Grouping Related State with reactive()
When several pieces of state naturally belong together — like a form's fields — grouping them into a single reactive() object can be clearer than declaring many separate ref()s, since it mirrors the shape of the data you're ultimately submitting. The tradeoff is that reactive() objects cannot be reassigned wholesale (you must mutate properties) and lose reactivity if destructured directly.
Cricket analogy: Grouping a batsman's stance, grip, and guard-mark into one reactive() "setup" object mirrors how a coach reviews them together as a single checklist — but you can't swap the whole object at once, and pulling out just "grip" alone loses its connection to live coaching feedback.
<script setup>
import { reactive } from 'vue'
const form = reactive({
email: '',
password: '',
rememberMe: false,
})
function resetForm() {
form.email = ''
form.password = ''
form.rememberMe = false
}
</script>Deriving State with computed() Instead of Duplicating It
A frequent anti-pattern is manually keeping a second ref in sync with a first one via a watcher, when a computed() would derive it automatically and eliminate an entire class of sync bugs. Local state should hold only the minimal 'source of truth' values; everything else that can be derived from them should be a computed property.
Cricket analogy: Manually watching a batsman's runs and updating a separate "strikeRate" ref by hand is fragile — a computed() that derives strike rate from runs and balls faced automatically stays correct, the way a scoreboard's strike-rate display should update itself, not be manually re-typed after every run.
<script setup>
import { ref, computed } from 'vue'
const items = ref([
{ name: 'Keyboard', price: 79, qty: 1 },
{ name: 'Mouse', price: 29, qty: 2 },
])
const subtotal = computed(() =>
items.value.reduce((sum, item) => sum + item.price * item.qty, 0)
)
</script>Extracting Reusable Local Patterns into Composables
When a local state pattern (like managing a boolean toggle, or tracking a debounced search input) repeats across several unrelated components, it's a signal to extract it into a composable function rather than a global store — the state stays local to each component instance that calls the composable, but the logic itself is shared.
Cricket analogy: When multiple broadcast graphics — one for batting stats, one for bowling stats — each need their own "isExpanded" toggle logic repeated, extracting a useToggle() composable shares the logic while each graphic keeps its own independent open/closed state.
A useful rule of thumb: state should live at the lowest common ancestor that actually needs it. Only promote state to Pinia when multiple, non-nested parts of the component tree genuinely need to read or mutate the same data — premature global state makes components harder to test in isolation and obscures data flow.
Destructuring a reactive() object, e.g. const { email } = form, breaks reactivity for that variable because it copies the primitive value at that instant, disconnecting it from the reactive proxy. Use toRefs(form) if you need to destructure while preserving reactivity, or prefer ref() for values you plan to destructure.
- Most UI state is naturally local; keep it in the component unless multiple unrelated components need it.
- reactive() suits grouped, form-shaped state; ref() suits standalone primitives or values you'll reassign wholesale.
- Prefer computed() over manually synced state + watchers for any value derivable from other local state.
- Repeated local state logic across components is a strong signal to extract a composable, not a global store.
- Destructuring a reactive() object loses reactivity; use toRefs() if destructuring is necessary.
- Promote state to Pinia only when it's genuinely shared across non-nested parts of the component tree.
Practice what you learned
1. What is a good rule of thumb for deciding where state should live in a Vue app?
2. Why does destructuring a reactive() object break reactivity for the extracted variables?
3. What is the recommended alternative to manually syncing a second ref with a watcher when a value can be derived from existing state?
4. When repeated local state logic (e.g. a debounced search input pattern) appears across several unrelated components, what is the recommended approach?
5. What utility preserves reactivity when you need to destructure properties out of a reactive() object?
Was this page helpful?
You May Also Like
reactive() and ref()
A deep dive into Vue 3's two core reactivity primitives — ref() for any value type and reactive() for objects — and when to use each.
Computed Properties
How Vue's computed() function derives cached, reactive values from other state, and why it's preferred over methods for derived data in templates.
Composables Explained
Understand what composables are, why they replace mixins as Vue's primary reuse pattern, and the conventions they follow.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics