Must-Know Reusable Module Vs Component In Vue 3 Composition API

Raja Tamil
Mar 2 · 9 min read

As a Vue.js developer, we want to keep our code clean and tidy by creating the optimal amount of reusable code.

With Composition API, we can absolutely do that by creating reusable JS modules aka hook functions and child components.

So what’s the difference between them?

When do we use one or the other?

Let’s find out!

JavaScript module aka hook function is a great way to separate business logic from the Vue component.

It’s completely reusable and UI-independent.

If you’re coming from MVC pattern, this would be the M (Model) part of it.

Vue components that are not page-based, meaning no route attached to it, aka Child components, are great when we want to reuse HTML template code aka UI that can be used in multiple parent components.

Now you have a better understanding of not only the differences but also which one to use when.

I’m going to demonstrate with some example code throughout this article to showcase when to use them.

Note: This article is intermediate and you should have the basic knowledge of using Vue.js or a similar framework.

Get A List Of Products

I used Firebase in the example below for the async operations.

I have an article on how to install Firebase to your vue project as well as on how to make Cloud Firestore queries if you’re interested.

First, create a product reactive array and call loadProduct() function inside onMounted() lifecycle method.

Recommended
Must-Know Ref vs Reactive Differences In Vue 3 Composition API

import firebase from "firebase";
import { onMounted, reactive } from "vue";
export default {
setup() { const products = reactive([]); onMounted(() => {
loadProducts();
});
const loadProducts = async () {
try {
const productsSnap = await firebase
.firestore()
.collection("products")
.orderBy("brand", "asc")
.get();
productsSnap.forEach((doc) => {
let product = doc.data();
product.id = doc.id;
products.push(product);
});
} catch (e) {
console.log("Error Loading Products");
}
}
return {
products,
};
},
};

Inside the loadProducts() declaration, use Firebase query to get a list of products that I’ve already stored in the Firebase Cloud Firestore database.

Finally, return the products array to use in the template.

Each product object has two keys that are:

  • title
  • upc

Nothing fancy.

This works great!

As the project evolves, let’s say I’ll need to create an inventory component where I want to use a list of products as a drop-down, for example, to select a product to create an inventory from.

I’ll have to rewrite loadProducts() code which I do not want to do.

With Vue 3 Composition API, we can easily extract the async code into a UI-independent JavaScript module and can reuse it wherever we want in our application.

Let’s see how to do that.

Recommended
Must-Know Ref vs Reactive Differences In Vue 3 Composition API

Reusable Product.js Module

Let’s see how to extract the loadProducts() code from the ProductList.vue to Product.js

Import Firebase as we’ll be using it for CRUD async operations.

Then declare useProduct() function as well as reactive state object inside the function which will have one property in it called products that is a type of array.

import { reactive, toRefs } from "vue";
import firebase from "firebase";
export default function useProduct() {
const state = reactive({
products: [],
});
const loadProducts = async() => {
try {
const productsSnap = await firebase.firestore().collection("products").orderBy("brand", "asc").get();
productsSnap.forEach(doc => {
let product = doc.data();
product.id = doc.id;
state.products.push(product);
});
} catch (e) {
console.log("Error Loading Products")
}
}
return { ...toRefs(state),
loadProducts
}
}

Then, create the loadProducts() function which gets the products data from the products collection in Cloud Firestore and gets the snapshot of it.

Loop through them and push each product into the products array.

Finally, return the state and loadProduct.

The reason I wrapped state with toRefs is to convert the reactive object into a plain JavaScript object so that I can destructure it when needed.

Now we have the Product.js module ready to use inside the ProductList.vue or any component that needs a list of products in our app.

ProductList.vue

First, import the product module at the top.

Secondly, call useProduct() function and destructure products and loadProducts inside the setup() function.

Then, invoke loadProducts() inside the onMounted() lifecycle method.

import { onMounted } from "vue";
import useProduct from "@/modules/product";
export default {
setup() {
const { products, loadProducts } = useProduct();
onMounted(async () => {
await loadProducts();
});
return {
products,
};
},
};

Finally, return the products which is the same as before.

At this stage, products are available in the template to use and we can loop through them using v-for to show however we want.

Recommended
Vue + Firestore ← Build A Simple CRUD App with Authentication

You can see how easy it is to create a module like Product.js to organize reusable UI-independent functionalities.

One more benefit of using modules like Product.js is to use them as state management as well.

Modules As State Management

import { reactive, toRefs } from "vue";
import firebase from "firebase";
const state = reactive({
products: [],
});
export default function useProduct() {
...
}

This way, I do not have to fetch data from Firebase every time I go to the ProductList.vue route.

Let’s take a look at creating a reusable component.

Reusable Vue Component

For example, let’s say we have two views in our application; one for creating a new product (ProductAdd.vue) and the other one for editing a product (ProductEdit.vue).

Both will have the same amount of form elements which is one of the great uses for creating reusable Vue components.

Let’s create a component called ProductForm.vue inside the src/components folder.

This will be a child component having form elements which then will be used by ProductAdd.vue and ProductEdit.vue files.

Recommended
Must-Know Property Creation Differences in Vue 2 and Vue 3

ProductForm.vue

<template>
<form @submit.prevent>
<h1>Create Product</h1>
<div class="field">
<label>Title</label>
<input type="text" v-model="product.title" />
</div>
<div class="field">
<label>UPC</label>
<input type="text" v-model="product.upc" />
</div>
</div>
<button class="ui button blue tiny" @click="productSaveButtonPressed">Save Product</button>
</form>
</template>

There is one button with the callback function.

Nothing is fancy.

Now, let’s declare the product object inside the setup() function that’s already bound to the input field in the template code above.

<script>
import { reactive } from "vue";
export default {

setup(_, {emit}) {
const product = reactive({
title: "",
upc: 0,
});
const saveProductButtonPressed = () => {
emit("product", product);
};
return { product, saveProductButtonPressed };
},
};
</script>

Every time, when a signup button is pressed, we want to send form data to the caller (parent component) using the emit method which can be destructured from the second argument called context in the setup function.

Finally, return product and saveProductButtonPressed so that template code will have access to them.

Now that we have a reusable view component, let’s use it inside ProductAdd.vue component.

ProductAdd.vue

First, import the ProductForm.vue component.

Then, add it to the components object.

<template>
<ProductForm @product="getProductData" />
</template>
<script>
import ProductForm from "@/components/ProductForm";
export default {
components: {
ProductForm,
},
setup() {
const getProductData = (product) => {
console.log(product)
};
return { getProductData };
},
};
</script>

Finally, capture the product data that is emitted by ProductForm.vue in the template using the getProductData() function and the actual value will be in the argument of the function signature.

Now that we have form data, let’s actually add the product to the Database.

Add Product In Product.js

...
export default function useProducts() {
...
const addProduct = async (product) => {
try {
await firebase.firestore().collection("products").doc().set(product);
} catch (e) {
console.log(e.message);
}
}
return { ...toRefs(state), loadProducts, addProduct }
}

Now the addProduct() function is ready to use inside ProductAdd.vue component, we can use it to actually insert data to the Firebase database.

<template>
<ProductForm @product="getProductData" />
</template>
<script>
import ProductForm from "@/components/ProductForm";
import useProduct from "@/modules/product";
export default {
components: {
ProductForm,
},
setup() {
const getProductData = async (product) => {
await addProduct(product);
};
return { getProductData };
},
};
</script>

Pretty simple!

Let’s see how to use the same ProductForm.vue component for editing a product.

ProductEdit.vue

When a user presses submit button, emit the updated form data to the ProductEdit.vue parent component to actually save it to the Database.

<template>
<ProductForm
@product="getProductData"
/>
</template>
<script>
import ProductForm from "@/components/ProductForm";
export default {
components: {
ProductForm,
},
setup() {
const getProductData = (product) => {
console.log(product)
};
return { getProductData };
},
};
</script>

Now we need to create two functions inside the Product.js module to handle editing a product properly.

One function is to load a product based on a product ID that the user wants to edit. The other function is to actually update that product with new data to the database.

Load And Edit Product In Product.js

Then, define loadProduct() which will take the product ID as an argument and get product data from Firebase, and set it to the product object.

...
const state = reactive({
products: [],
product: {},
});
export default function useProduct() {
...
const loadProduct = async (id) => {
try {
const productSnap = await firebase.firestore().collection("products").doc(id).get();
state.product = productSnap.data();
} catch (e) {
console.log(e.message);
}
} const updateProduct = async (id, product) => {
try {
await firebase.firestore().collection("products").doc(id).update(product);
} catch (e) {
console.log(e.message);
}
}
return { ...toRefs(state), loadProducts, loadProduct, updateProduct, addProduct }
}

Next, one is updateProduct() which will take two arguments: product ID and the actual product data that needs to be updated.

Finally, return them so that we can access them in the ProductEdit.vue component.

Now, let’s get the product we want to edit and populate the product data to the form fields.

Update A Product

<template>
<ProductForm
:editProduct="product"
@product="getProductData"
/>
</template>
<script>
import { onMounted } from "vue";
import { useRoute } from "vue-router";
import useProduct from "@/modules/product";
import ProductForm from "@/components/ProductForm";
export default {
components: {
ProductForm,
},
setup() {
const route = useRoute();
const {
product,
loadProduct,
} = useProduct();
onMounted(async () => {
await loadProoduct(route.params.id)
});
const getProductData = (product) => {
console.log(product)
};
return { product, getProductData };
},
};
</script>

Once the product data is received in the ProductEdit.vue component, send it to the ProductForm.vue template as a value of :editProduct attribute.

Populated Product Data In The Form

Then, loop through editProduct properties and set an appropriate value to the product object using key.

import { onMounted, reactive } from "vue";export default { props: {
editProduct: Object,
},
onMounted(() => {
if (props.editProduct) {
for (let key in props.editProduct) {
product[key] = props.editProduct[key];
}
}
...
}

At this stage, all the input fields will be pre-populated with product data.

Update Product Data

As soon as the save button is pressed, product data will be emitted to its caller, in this case, the ProductEdit.vue component.

Similar to the previous example, we can use the getProductData() function to capture the data.

...
const {
product,
loadProduct,
updateProduct
} = useProduct();
... const getProductData = (product) => {
await updateProduct(route.params.id, product);
};
...
},
};
</script>

Finally, call the updateProduct() function with product ID and the product object that we want to update with new data to the database.

That’s it!

Conclusion

To recap, JavaScript module aka hook function is a great way to separate logic from the Vue component. It’s completely reusable and UI-independent.

Vue components, on the other hand, are great when you want to reuse template code, aka child component, that can be used in multiple parent components.

Now you have a better understanding of not only the differences but also which one to use.

I hope it helps!

Feel free to connect with me if you’ve any suggestions or feedback. I always like to improve my articles and provide great value to the readers.

Happy Coding!

Vue.js Developers

Helping web professionals up their skill and knowledge of…

Sign up for Vue.js Developers Newsletter

By Vue.js Developers

A weekly publication with the best Vue.js news and articles. Take a look.

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Raja Tamil

Written by

Instructor/Owner of SoftAuthor! http://softauthor.com/

Vue.js Developers

Helping web professionals up their skill and knowledge of Vue.js

Raja Tamil

Written by

Instructor/Owner of SoftAuthor! http://softauthor.com/

Vue.js Developers

Helping web professionals up their skill and knowledge of Vue.js

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store