In Vue.js, composables are functions that encapsulate reactive logic and can be reused in 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 the 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 that manages 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,
useCounter
is a composable that encapsulates the logic of a counter.- It returns a reactive value (
count
) and two methods (increment
anddecrement
).
Using a composable in a component
Now we can use our composable in our component. To do this, we simply need to import the function and call it within the setup
.
For example, let’s see how to use our composable useCounter
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 composable
useCounter
to manage the state of the counter. - The logic of the counter is encapsulated in the composable, which makes 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 errors that occur.
Using the composable useFetch
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 the state of a form.
// 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 manages the state of a form. It returns the state of the form and methods to update and reset the fields.
Using the composable useForm
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>