In Vue.js, composables are functions that encapsulate reactive logic and can be reused across multiple components.
Composables allow us to organize and reuse logic in a modular way, which is especially useful in complex applications.
They are an evolution of the mixins that existed in Vue.js 2, but more flexible and easier to maintain. If you see mixins, it’s an obsolete syntax.
Basic Syntax of a Composable
A composable is simply a function that uses Composition API functions, (like ref, reactive, computed, and watch).
By convention, composables are named starting with ‘use’. But it’s just a convention.
Otherwise, they are “normal” functions. They can return reactive values, methods, or any other logic we need.
Let’s see an example by creating a composable to manage the state of a counter. For this, we create the file useCounter.js, with this content.
import { ref } from 'vue';
export function useCounter() {
const count = ref(0);
function increment() {
count.value++;
}
function decrement() {
count.value--;
}
return {
count,
increment,
decrement,
};
}
In this example,
useCounteris a composable that encapsulates the logic of a counter.- It returns a reactive value (
count) and two methods (incrementanddecrement).
Using a Composable in a Component
Now we can use our composable in our component. To do this, we simply have to import the function and call it inside setup.
For example, let’s see how to use our useCounter composable in a component.
<script setup>
import { useCounter } from './useCounter';
const { count, increment, decrement } = useCounter();
</script>
<template>
<div>
<p>Counter: {{ count }}</p>
<button @click="increment">Increment</button>
<button @click="decrement">Decrement</button>
</div>
</template>
In this example,
- The component uses the
useCountercomposable to manage the counter state. - The counter logic is encapsulated in the composable, making the component cleaner and easier to maintain.
Practical Examples of Composables
Composable for Handling API Calls
Suppose we want to create a composable to handle API calls.
// useFetch.js
import { ref } from 'vue';
export function useFetch(url) {
const data = ref(null);
const loading = ref(true);
const error = ref(null);
async function fetchData() {
try {
const response = await fetch(url);
data.value = await response.json();
} catch (err) {
error.value = err;
} finally {
loading.value = false;
}
}
fetchData();
return {
data,
loading,
error,
};
}
In this example, useFetch is a composable that handles an API call. It returns the data, loading state, and any error that occurs.
Using the useFetch composable in a component
<template>
<div>
<p v-if="loading">Loading...</p>
<p v-else-if="error">Error: {{ error.message }}</p>
<p v-else>Data: {{ data }}</p>
</div>
</template>
<script setup>
import { useFetch } from './useFetch';
const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/posts/1');
</script>
Composable for Handling Form State
Suppose we want to create a composable to handle form state.
// useForm.js
import { ref } from 'vue';
export function useForm(initialState) {
const form = ref({ ...initialState });
function updateField(field, value) {
form.value[field] = value;
}
function resetForm() {
form.value = { ...initialState };
}
return {
form,
updateField,
resetForm,
};
}
In this example, useForm is a composable that handles the state of a form. It returns the form state and methods to update and reset fields.
Using the useForm composable in a component
<template>
<form @submit.prevent="handleSubmit">
<input v-model="form.name" @input="updateField('name', $event.target.value)" placeholder="Name" />
<input v-model="form.email" @input="updateField('email', $event.target.value)" placeholder="Email" />
<button type="submit">Submit</button>
<button type="button" @click="resetForm">Reset</button>
</form>
</template>
<script setup>
import { useForm } from './useForm';
const { form, updateField, resetForm } = useForm({ name: '', email: '' });
function handleSubmit() {
console.log('Form submitted:', form.value);
}
</script>
