LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 2517 min read

Composables (Custom Hooks)

Extract and reuse stateful logic across components by writing your own composable functions.

What Is a Composable?

A composable is a plain JavaScript function that uses Composition API functions (ref, computed, watch, lifecycle hooks) to encapsulate and expose a piece of reusable, stateful logic. It's Vue's answer to what React calls a "custom hook."

Writing Your First Composable

A simple counter, extracted into a reusable function so any component can get its own independent instance of this behavior.

// composables/useCounter.js
import { ref } from 'vue';
export function useCounter(initialValue = 0) {
const count = ref(initialValue);
function increment() {
count.value++;
}
function decrement() {
count.value--;
}
return { count, increment, decrement };
}
<script setup>
import { useCounter } from '../composables/useCounter';
const { count, increment, decrement } = useCounter(10);
</script>
<template>
<button @click="decrement">-</button>
{{ count }}
<button @click="increment">+</button>
</template>

A Data-Fetching Composable

Composables shine for repeated patterns — like the loading/error/data cycle around a fetch call, written once and reused everywhere it's needed.

// composables/useFetch.js
import { ref, watchEffect } from 'vue';
export function useFetch(url) {
const data = ref(null);
const error = ref(null);
const loading = ref(true);
watchEffect(async () => {
loading.value = true;
try {
const res = await fetch(url.value ?? url);
data.value = await res.json();
} catch (err) {
error.value = err;
} finally {
loading.value = false;
}
});
return { data, error, loading };
}
<script setup>
import { useFetch } from '../composables/useFetch';
const { data: posts, loading, error } = useFetch('/api/posts');
</script>
<template>
<p v-if="loading">Loading…</p>
<p v-else-if="error">Something went wrong.</p>
<ul v-else>
<li v-for="post in posts" :key="post.id">{{ post.title }}</li>
</ul>
</template>

Composable Naming Convention

By strong convention, composables are named starting with use — useCounter, useFetch, useMousePosition — making it immediately clear in code that a function is a composable, not a plain utility function.

Common Beginner Mistakes

Calling a composable conditionally or inside a loop

Like React hooks, composables that use lifecycle hooks should be called unconditionally at the top level of setup(), so Vue can correctly associate them with the current component instance.

Sharing one instance of a composable's state across every component

Calling useCounter() inside each component gives each one its own independent state — if you actually want shared state across the whole app, that's what Pinia is for.

FAQs

Yes — composables compose naturally, just like regular functions calling other functions.

No strict requirement, but composables/ at the project root (or per-feature) is the near-universal convention.

Summary

Composables let you extract and reuse stateful logic as plain functions, avoiding duplication across components. Next, you'll see the Options API up close and how it maps onto everything you've learned so far.

Next Lesson →

Options API vs Composition API