Composables

Vue · Practical · 13 min

Composables are the main reuse mechanism of the Composition API, replacing mixins.

A composable is a function that uses Vue's Composition API to encapsulate stateful logic, named useSomething by convention. It can create refs, computeds and watchers and register lifecycle hooks, which then belong to the component that called it.

export function useMouse() {
  const x = ref(0), y = ref(0)
  const update = (e) => { x.value = e.pageX; y.value = e.pageY }
  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))
  return { x, y }
}

Conventions that make composables pleasant to use:

  • Return a plain object of refs, not a reactive(): callers can destructure (const { x, y } = useMouse()) without losing reactivity.
  • Accept refs, getters or plain values as arguments and normalise them with toValue(). Read them inside a watchEffect or a watched getter so the composable reacts when the input changes.
  • Call composables synchronously in setup (like lifecycle hooks), so their hooks attach to the right component.
  • Clean up what you set up (listeners, intervals, subscriptions).

Each call creates independent state. If you want state shared between components, create it outside the function (module scope) or use a store such as Pinia.

Before writing one, check VueUse, a large collection of ready-made composables (useLocalStorage, useFetch, useIntersectionObserver and many more).

Example

// useFetch.js
import { ref, watchEffect, toValue } from 'vue'

export function useFetch(url) {          // url: string, ref or getter
  const data = ref(null), error = ref(null), loading = ref(false)

  watchEffect(async (onCleanup) => {
    const controller = new AbortController()
    onCleanup(() => controller.abort())
    loading.value = true
    error.value = null
    try {
      const res = await fetch(toValue(url), { signal: controller.signal })
      data.value = await res.json()
    } catch (e) {
      if (e.name !== 'AbortError') error.value = e
    } finally {
      loading.value = false
    }
  })
  return { data, error, loading }
}

// In a component: const { data, loading } = useFetch(() => `/api/posts/${id}`)

Remember

  • A composable is a useX() function that packages refs, computeds, watchers and hooks
  • Return an object of refs; accept refs/getters and read them with toValue()
  • Each call has its own state; module-level state or Pinia shares it

Watch out for

  • Returning reactive({ x, y }) so callers lose reactivity when they destructure.

Check yourself

  1. Two components each call `useCounter()`, which creates `const count = ref(0)` inside the function. Clicking + in one component...

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: vuejs.org/guide/reusability/composables.html