Skip to content
advanced Phase 7 · Lifecycles and Composables

Flexible Composable Parameters

Accept refs, getters, or plain values while keeping the composable reactive.

45m
0 problems
Topic Progress 0%

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 type expresses those shapes, and toValue resolves a value, ref, or getter to its current value. The important detail is where the read occurs: resolving once in setup creates a snapshot, while reading inside a computed or watcher getter lets Vue track dependencies and rerun work when the source changes.

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 when both fixed and changing inputs are part of the contract. Return refs or computed refs with explicit types so consumers know which values update. Do not widen to any because several input shapes are accepted. Normalize the input at one boundary and keep side effects in a watcher that observes the normalized source.

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, or getter returning a string. Inside the composable, create a computed normalized query using toValue(query). Watch that computed and fetch results when it changes. A plain string produces one initial result; a ref or getter can cause future requests. Make the initial-run behavior explicit with the watch options rather than surprising a caller.

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 and return results typed from T or a derived result. Avoid wrapping every input in a new ref if that changes ownership or causes synchronization loops. The caller owns the source; the composable observes it and owns only its derived request state.

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?

Question 1 options

2. When reviewing Flexible Composable Parameters, which point must hold?

Question 2 options

3. Where should toValue run when a composable must react to a ref or getter?

Question 3 options

4. What does a plain primitive argument provide to a composable?

Question 4 options

Flashcards

Question

What is Flexible Composable Parameters?

Answer

Accept refs, getters, or plain values while keeping the composable reactive.

Question

Flexible Composable Parameters: why it matters

Answer

MaybeRefOrGetter parameters let callers pass a static value or a reactive source interchangeably.

Question

Flexible Composable Parameters: key detail

Answer

toValue resolves a getter or ref into a plain value at read time inside watchers.

Question

Flexible Composable Parameters: practical note

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?

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.

Related Reading