VuVue · Lesson 5 of 7

Composables

Composables are Vue 3's way of extracting and sharing reactive logic between components. They're just functions that use Vue's Composition API — a composable is to Vue what a custom hook is to React.

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

export function useFetch(url) {
  const data    = ref(null)
  const error   = ref(null)
  const loading = ref(false)

  watchEffect(async () => {
    const resolvedUrl = toValue(url)   // works with ref or plain string
    if (!resolvedUrl) return

    data.value    = null
    error.value   = null
    loading.value = true

    try {
      const res = await fetch(resolvedUrl)
      if (!res.ok) throw new Error(`HTTP ${res.status}`)
      data.value = await res.json()
    } catch (e) {
      error.value = e
    } finally {
      loading.value = false
    }
  })

  return { data, error, loading }
}
HTML
<!-- Using the composable -->
<template>
  <div>
    <div v-if="loading">Loading...</div>
    <div v-else-if="error">Error: {{ error.message }}</div>
    <ul v-else>
      <li v-for="post in data" :key="post.id">{{ post.title }}</li>
    </ul>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import { useFetch } from './composables/useFetch'

// Works with a static URL
const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/posts')

// Also works reactively — refetches when postId changes!
const postId = ref(1)
const { data: post } = useFetch(() => `https://jsonplaceholder.typicode.com/posts/${postId.value}`)
</script>

<!-- composables/useLocalStorage.js -->
<script>
import { ref, watch } from 'vue'

export function useLocalStorage(key, defaultValue) {
  const stored = localStorage.getItem(key)
  const value  = ref(stored ? JSON.parse(stored) : defaultValue)

  watch(value, (newVal) => {
    localStorage.setItem(key, JSON.stringify(newVal))
  }, { deep: true })

  return value
}
</script>

<!-- Using useLocalStorage -->
<script setup>
import { useLocalStorage } from './composables/useLocalStorage'

const theme    = useLocalStorage('theme', 'light')
const settings = useLocalStorage('settings', { notifications: true, lang: 'en' })
</script>
✦ Tip
Name composables starting with `use` (like `useFetch`, `useMousePosition`, `useTheme`). Put them in a `composables/` directory. Any logic that involves reactive state and could be used in multiple components is a good candidate for extraction into a composable.