Anatomy of a Vue composable file.
Composables are well-documented, but some features might remain a mystery, such as the ability to have shared state across components. Personally I usually use composables as files and make use of shared state that often replace the need for a state manages like Pinia/Vuex.
In this example we have an app that has some e-commerce features like browsing the product catalog by category. All products are loaded in the background in a shared state and the component instances have a filtered list of products by categoryId
.
// useProducts.js
import { ref, computed } from 'vue'
import axios from 'axios'
// Products variable is a state that is shared over different instances!
const products = ref([])
// A function to populate the products data.
// We can also add conditions where this is done only once!
const loadProducts = async () => {
const { rows } = await axios.get('/products')
products.value = rows
}
/**
* @param {Object} settings instance specific settings for initialization
* @param {Number} settings.categoryId a categoryId is provided in this example
* @returns {Object}
*/
export default function useProducts({ categoryId }) {
// Rows is a computed value that exists in this instance.
const rows = computed(() => {
return products.value.filter(row => row.categoryId === categoryId)
})
// Run some data initializing code when this composable is being used.
// You can also use some Vue hooks like onMounted here.
loadProducts()
return {
rows,
}
}
And to use this in a component simply import the file and use the setup
function. The categoryId
here could originate from component properties or from the route parameters.
// ProductList.vue
import useProducts from 'useProducts.js'
export default {
setup() {
const { rows: products } = useProducts({ categoryId: 7 })
return {
products,
}
}
}
And that's it! This simple paradigm is very extendable and re-usable. The key points here are: shared state vs instance specific state or computed values and running initializing code.