Flexible Composable Parameters: Core Concepts
Accept values without losing reactivity
A composable can accept a plain value, a ref, or a getter when callers legitimately need both static and reactive inputs. Vue's MaybeRefOrGetter
For example, a composable that loads a record by id should accept a reactive source if route navigation can change the id without remounting the view. A function that formats a fixed display label may only need a plain string. Use flexibility when real consumers benefit from it, not as a default for every parameter. Document whether a plain value runs once or whether callers must pass a ref/getter for updates.
Track the source in a reactive context
This loses reactivity:
const id = toValue(source)
watch(() => id, load)
The watcher observes an ordinary local value, not the original source. Instead, use a getter that resolves the input during dependency tracking: watch(() => toValue(source), load). A computed can normalize the same input and then be watched. This is useful for props, route params, and filters whose values can change while a component instance remains mounted.
If a getter returns a new object on every evaluation, identity can trigger unnecessary work even when its contents are equivalent. Prefer a stable primitive key or compute a normalized value with deliberate equality semantics. For several related parameters, derive one request key or watch a small set of specific fields instead of the entire route object.
toValue, toRef, and toRefs differ
toValue reads a MaybeRefOrGetter at the time it is called. toRef can create a ref linked to a property of a reactive object, such as toRef(props, 'query'). toRefs converts an object's properties into refs, which helps preserve reactivity when destructuring. These utilities solve different problems; using one as a substitute for another can silently turn a live property into a snapshot.
Plain destructuring from a reactive object can lose the proxy connection for primitive properties. A composable that accepts props should accept the object, a getter, or a linked ref depending on its contract. Do not assume that passing props.query into a parameter preserves future updates; that expression evaluates to the current string. Pass () => props.query or toRef(props, 'query') when the composable must react.
Define a typed, understandable API
Use MaybeRefOrGetter
Reactive input APIs can hide timing. Explain when a network request runs, whether an initial request is immediate, how rapid changes are debounced, and what happens when an old request finishes late. Include cancellation or a generation check for overlapping async work. Document whether prior data remains visible during a change and when errors reset.
If the composable only needs an immutable option, use a plain value and keep the API simple. If it needs live updates, require or accept a reactive source. The design is clear when callers can tell whether changing their input will trigger recomputation or I/O without inspecting the implementation.
Flexible Composable Parameters: Practice
Build a reactive search composable
Create useSearch(query) that supports a string, Ref
Debounce frequent edits and cancel the previous timer when a new query arrives. If a request is already in flight, abort it where possible and ignore its response if its request key is no longer current. Clear an error when a new request begins, and decide whether existing results remain visible while the next query is loading. Return results, pending, error, and refresh as reactive values or methods with a small typed contract.
Test each input form
Mount a host component with a static query and verify one initial request. Mount with a ref, change it, and verify the composable searches the new value. Use a getter derived from a prop or route query and confirm updates are tracked. If toValue is called before the watcher is created, the ref test should fail because the composable captured a snapshot; this is a useful regression case.
Resolve two requests out of order and verify the earlier result cannot overwrite the latest query. Advance fake timers to check debounce behavior and ensure unmount cancels a pending timer. Test network failure, retry, empty input, and a short-query policy. Restore timers and mocks between tests so the shared test environment does not retain effects.
Choose the right conversion utility
Use toValue when an API accepts a MaybeRefOrGetter and must read its current value. Use toRef(props, 'query') when the composable needs a property ref linked to a reactive object. Use toRefs to convert multiple reactive properties before destructuring. Do not expect toRef of a plain primitive to reconnect it to a source that was already evaluated.
Preserve TypeScript inference. A generic composable can accept MaybeRefOrGetter
Handle input changes as a state machine
An input update can cancel a debounce, abort a request, preserve old results, clear an error, and start a new request. Write these transitions down. If an empty query should clear results, do so synchronously and skip the request. If the query is invalid, return a validation state rather than sending it. If a request cannot be aborted, use a sequence number so only the active request may commit data.
The composable is reusable when static and reactive consumers behave predictably, types explain the contract, and cleanup follows the owning scope. The type union is only the surface; correct dependency tracking and stale-work handling make it safe.
Keep this API contract in the composable's type documentation.
Flexible Composable Parameters: Trade-offs and Interviews
Flexibility can obscure timing
MaybeRefOrGetter accepts several forms but can hide whether a value is read once or observed over time. toValue outside a reactive effect returns a snapshot. Inside a computed or watch getter, Vue tracks the ref or getter dependency. State this contract in the function documentation and test both a plain value and a changing source.
Do not accept a reactive source when the composable has no reason to observe changes. A plain immutable option is simpler and avoids unnecessary watcher behavior. Flexible inputs fit composables that are used with both constants and reactive props, such as a resource loader that can take a fixed id or a route-derived id.
Preserve dependency and object semantics
toValue reads, toRef links a property, and toRefs preserves multiple properties during destructuring. Plain destructuring of a primitive from a reactive object can lose the proxy connection. A getter that creates a fresh object may trigger unnecessary updates. Normalize to stable keys and watch the smallest meaningful dependency.
Async effects require more than reactivity. Cancel or ignore stale requests, define loading and error transitions, and clean up timers in the component scope. If multiple parameters jointly define a request, construct a stable key and ensure cache identity includes every input that changes the result.
In an interview, contrast a primitive snapshot with a ref or getter, explain why toValue belongs inside a tracked getter, and describe a test that changes the input after setup. Mention when a simple non-reactive API is preferable. This shows reactivity is an explicit behavioral contract, not just a TypeScript union.
When multiple callers use the same composable, verify that each owns its own debounce timer and cancellation controller. Shared input sources can still drive separate requests, but one consumer must not cancel work owned by another. If caching is desired, introduce a cache with an explicit key and lifetime rather than accidentally sharing a module-level request variable.
Consider whether updates are synchronous or scheduled. Vue watchers may be batched, and a rapid sequence of changes can collapse into one observed value. If every keystroke matters for an audit or event stream, use an explicit event path rather than assuming a watcher records every intermediate state. For ordinary search, batching and debounce are usually desirable.
An API accepting a getter should avoid invoking it outside the caller's setup context if it relies on injected state. Keep composable invocation synchronous and resolve the getter within the reactive callback. If a caller passes a plain function that reads non-reactive mutable state, Vue cannot track changes that the function does not expose. State this limitation and accept a ref when updates must be observable. Set immediate watcher behavior explicitly when the initial value should load without a change.
During review, ask what triggers recomputation, whether equality is stable, who cancels obsolete work, and what the caller sees while a request is pending. These questions make the reactivity contract testable. A flexible signature is useful only when the implementation treats each accepted form consistently and the lifecycle remains predictable.
Quiz
1. Which statement about Flexible Composable Parameters is accurate?
2. When reviewing Flexible Composable Parameters, which point must hold?
3. Where should toValue run when a composable must react to a ref or getter?
4. What does a plain primitive argument provide to a composable?
Flashcards
Question
What is Flexible Composable Parameters?
Click to reveal answer
Answer
Accept refs, getters, or plain values while keeping the composable reactive.
Question
Flexible Composable Parameters: why it matters
Click to reveal answer
Answer
MaybeRefOrGetter parameters let callers pass a static value or a reactive source interchangeably.
Question
Flexible Composable Parameters: key detail
Click to reveal answer
Answer
toValue resolves a getter or ref into a plain value at read time inside watchers.
Question
Flexible Composable Parameters: practical note
Click to reveal answer
Answer
Accepting a getter as an option keeps watch sources lazy without losing reactivity.
Question
Where should toValue run when a composable must react to a ref or getter?
Click to reveal answer
Answer
Reading within a reactive getter lets Vue track the underlying ref or getter dependencies.
Revision Notes
Key Takeaways
- 1. MaybeRefOrGetter parameters let callers pass a static value or a reactive source interchangeably.
- 2. toValue resolves a getter or ref into a plain value at read time inside watchers.
- 3. Accepting a getter as an option keeps watch sources lazy without losing reactivity.
Interview Tips
- • Explain Flexible Composable Parameters with one concrete example from real vue work, not a textbook restatement.
- • Discuss the trade-off: when Flexible Composable Parameters helps and when a lighter approach wins.
- • Place Flexible Composable Parameters inside the Lifecycles and Composables phase — interviewers check that you see the progression, not isolated facts.
Cheat Sheet
Flexible Composable Parameters Cheat Sheet
Phase: Lifecycles and Composables | Level: advanced
Key points:
- MaybeRefOrGetter parameters let callers pass a static value or a reactive source interchangeably.
- toValue resolves a getter or ref into a plain value at read time inside watchers.
- Accepting a getter as an option keeps watch sources lazy without losing reactivity.
- Document which arguments must be refs so callers do not accidentally pass static copies.
Tags: vue, composables, reactivity
Continue Learning
Before You Start
- No prerequisites listed.