Composables

Back

Loading concept...

๐Ÿงฐ Vue.js Composables: Your Magical Toolbox

The Big Idea

Imagine you have a magic toolbox. Inside this toolbox, you keep all your favorite tools - a hammer, a screwdriver, measuring tape. Whenever you need to build something, you just grab the right tool!

Composables in Vue.js are exactly like that magic toolbox. Theyโ€™re reusable pieces of code that you can grab and use in any component. Instead of writing the same code over and over, you write it once and share it everywhere!


๐ŸŽฏ What Are Composables?

A composable is a function that uses Vueโ€™s Composition API to package reusable logic.

Think of it this way:

  • ๐Ÿช Without composables: Baking the same cookie recipe from scratch every single time
  • ๐Ÿง With composables: Having a ready-made cookie dough mix you just use whenever you want cookies!

Simple Example

// Without composable (repeating code)
// In Component A:
const count = ref(0)
const double = computed(() => count.value * 2)

// In Component B (same thing again!):
const count = ref(0)
const double = computed(() => count.value * 2)
// With composable (write once, use everywhere!)
// useCounter.js
export function useCounter() {
  const count = ref(0)
  const double = computed(() => count.value * 2)
  return { count, double }
}

// Now ANY component can use it:
const { count, double } = useCounter()

๐Ÿ› ๏ธ Creating Composables

Creating a composable is like building your own tool. Hereโ€™s how:

Step 1: Create a New File

Put your composable in the composables/ folder:

src/
  composables/
    useCounter.js    โ† Your composable lives here!

Step 2: Write the Function

// composables/useCounter.js
import { ref, computed } from 'vue'

export function useCounter(initialValue = 0) {
  // State
  const count = ref(initialValue)

  // Computed
  const isPositive = computed(() => {
    return count.value > 0
  })

  // Methods
  function increment() {
    count.value++
  }

  function decrement() {
    count.value--
  }

  // Return what you want to share
  return {
    count,
    isPositive,
    increment,
    decrement
  }
}

The Recipe ๐Ÿณ

Every composable follows this simple recipe:

graph TD A["1. Import Vue stuff"] --> B["2. Create state with ref/reactive"] B --> C["3. Add computed properties"] C --> D["4. Add methods/functions"] D --> E["5. Return everything you need"]

๐ŸŽฎ Using Composables

Using a composable is as easy as calling a function!

In Your Component

<script setup>
import { useCounter } from '@/composables/useCounter'

// Just call the function!
const { count, isPositive, increment, decrement } = useCounter()

// You can even start with a different value
const scores = useCounter(100)
</script>

<template>
  <div>
    <p>Count: {{ count }}</p>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
  </div>
</template>

Real Life Example: Mouse Position

// composables/useMouse.js
import { ref, onMounted, onUnmounted } from 'vue'

export function useMouse() {
  const x = ref(0)
  const y = ref(0)

  function update(event) {
    x.value = event.pageX
    y.value = event.pageY
  }

  onMounted(() => {
    window.addEventListener('mousemove', update)
  })

  onUnmounted(() => {
    window.removeEventListener('mousemove', update)
  })

  return { x, y }
}
<script setup>
import { useMouse } from '@/composables/useMouse'

const { x, y } = useMouse()
</script>

<template>
  <p>Mouse is at: {{ x }}, {{ y }}</p>
</template>

๐Ÿ“ Composable Conventions

Just like there are rules for driving on the road, there are conventions for composables!

Rule 1: Start with โ€œuseโ€

Always name your composable starting with use:

โœ… Good Names โŒ Bad Names
useCounter counter
useMouse mouseTracker
useFetch fetchData
useAuth authentication

Rule 2: One File, One Composable

Keep each composable in its own file:

composables/
  useCounter.js      โœ… Good
  useMouse.js        โœ… Good
  useAuth.js         โœ… Good

Rule 3: Call at the Top

Always call composables at the top of setup():

// โœ… Good - at the top
const { count } = useCounter()
const { x, y } = useMouse()

// โŒ Bad - inside conditions or loops
if (something) {
  const { count } = useCounter() // Don't do this!
}

Rule 4: Keep It Focused

Each composable should do ONE thing well:

// โœ… Good - does one thing
function useCounter() { /* just counting */ }
function useAuth() { /* just authentication */ }

// โŒ Bad - does too many things
function useEverything() {
  /* counting AND auth AND mouse */
}

๐Ÿ“ฆ Composable Return Values

What should your composable give back? Think of it like a gift box!

Pattern 1: Return an Object (Most Common)

export function useCounter() {
  const count = ref(0)
  const increment = () => count.value++

  return {
    count,      // state
    increment   // method
  }
}

// Usage: destructure what you need
const { count, increment } = useCounter()

Pattern 2: Return Reactive Object

import { reactive, toRefs } from 'vue'

export function useUser() {
  const state = reactive({
    name: '',
    email: '',
    isLoggedIn: false
  })

  return toRefs(state)
}

// Usage
const { name, email, isLoggedIn } = useUser()

Pattern 3: Return a Single Value

export function useWindowWidth() {
  const width = ref(window.innerWidth)
  // ... event listeners
  return width  // just one value
}

// Usage
const width = useWindowWidth()

What to Include in Return

graph LR A["Return Value Contents"] --> B["๐Ÿ“ฆ State - refs/reactive"] A --> C["๐Ÿงฎ Computed - calculated values"] A --> D["๐ŸŽฏ Methods - functions to call"] A --> E["๐Ÿ”ง Utilities - helper functions"]

โณ Async Composables

Sometimes you need to fetch data from the internet. Async composables handle this!

The Fetch Pattern

// composables/useFetch.js
import { ref } from 'vue'

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

  async function fetchData() {
    loading.value = true
    error.value = null

    try {
      const response = await fetch(url)
      data.value = await response.json()
    } catch (e) {
      error.value = e.message
    } finally {
      loading.value = false
    }
  }

  // Fetch immediately
  fetchData()

  return {
    data,
    error,
    loading,
    refetch: fetchData
  }
}

Using Async Composables

<script setup>
import { useFetch } from '@/composables/useFetch'

const { data, loading, error, refetch } = useFetch(
  'https://api.example.com/users'
)
</script>

<template>
  <div v-if="loading">Loading... โณ</div>
  <div v-else-if="error">Error: {{ error }} ๐Ÿ˜ข</div>
  <div v-else>
    <ul>
      <li v-for="user in data" :key="user.id">
        {{ user.name }}
      </li>
    </ul>
    <button @click="refetch">Refresh</button>
  </div>
</template>

The Three States

Every async operation has three states:

State Whatโ€™s Happening
โณ Loading Waiting for data
โœ… Success Got the data!
โŒ Error Something went wrong

๐Ÿ  Stateful Composables

Stateful composables remember things between uses. Itโ€™s like having a shared notebook everyone can read and write in!

Shared State (Singleton Pattern)

// composables/useSharedCounter.js
import { ref } from 'vue'

// State OUTSIDE the function = shared!
const globalCount = ref(0)

export function useSharedCounter() {
  function increment() {
    globalCount.value++
  }

  function decrement() {
    globalCount.value--
  }

  return {
    count: globalCount,
    increment,
    decrement
  }
}

How It Works

graph TD A["Component A"] --> C["Shared State"] B["Component B"] --> C D["Component C"] --> C C --> E["globalCount = 5"]

All components see the SAME value!

Independent vs Shared State

// INDEPENDENT - each component gets its own copy
export function useCounter() {
  const count = ref(0)  // Inside = independent
  return { count }
}

// SHARED - all components share one copy
const count = ref(0)  // Outside = shared
export function useSharedCounter() {
  return { count }
}
Type State Location Use Case
Independent Inside function Each component needs its own data
Shared Outside function All components need same data

Real Example: Shopping Cart

// composables/useCart.js
import { ref, computed } from 'vue'

// Shared state - one cart for the whole app!
const items = ref([])

export function useCart() {
  const totalItems = computed(() => {
    return items.value.length
  })

  const totalPrice = computed(() => {
    return items.value.reduce((sum, item) => {
      return sum + item.price
    }, 0)
  })

  function addItem(item) {
    items.value.push(item)
  }

  function removeItem(id) {
    items.value = items.value.filter(i => i.id !== id)
  }

  function clearCart() {
    items.value = []
  }

  return {
    items,
    totalItems,
    totalPrice,
    addItem,
    removeItem,
    clearCart
  }
}

๐ŸŽ Putting It All Together

Hereโ€™s the complete picture of composables:

graph LR A["Composables"] --> B["Creating"] A --> C["Using"] A --> D["Conventions"] A --> E["Return Values"] A --> F["Async"] A --> G["Stateful"] B --> B1["Function with use prefix"] C --> C1["Call in setup"] D --> D1["Naming rules"] E --> E1["Object with state &amp; methods"] F --> F1["Handle loading/error/data"] G --> G1["Share state across components"]

Quick Reference

Concept Remember This
Composables Reusable logic functions
Creating Start with use, return an object
Using Call at top of setup
Conventions use prefix, one file each
Return Values State + computed + methods
Async Handle loading, error, data
Stateful State outside = shared

๐Ÿš€ You Did It!

You now understand Vue.js Composables! Remember:

  1. ๐Ÿงฐ Composables are your magic toolbox
  2. โœ๏ธ Create them with the use prefix
  3. ๐Ÿ“ž Call them at the top of setup
  4. ๐Ÿ“ Follow the conventions for clean code
  5. ๐Ÿ“ฆ Return state, computed, and methods
  6. โณ Handle async with loading states
  7. ๐Ÿ  Use stateful composables for shared data

Now go build amazing things! ๐ŸŽ‰

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.