Telerik blogs

Let’s explore a couple of common pitfalls and mistakes when creating composition functions/composables for Vue 3.

Vue 3’s composition API has literally changed the way we approach component creation. With it we’ve received an incredibly powerful tool, the composition function or composables.

Through my everyday experience working with a team of skilled Vue developers, I’ve noticed a couple common mistakes or pitfalls that don’t necessarily break your application or create an error, but nevertheless can create hard to debug situations and spaghetti code.

So without further ado, let’s jump right in.

Creating Side Effects Within the Composable

One of the most common pitfalls (and one of the hardest ones to debug when they go wrong) is the creation of side effects within a composition function.

A side effect is any piece of code that creates an effect outside of the context of your composable.

Let’s look at some common side effects.

Using Provide/Inject Within the Composable

This is extremely problematic. When looking at a component that uses composition functions, it is not evident that that component may be providing or injecting variables. Therefore, whenever a problem arises with said variable, it can be a real struggle to figure out exactly where the problem is being created.

Setting or Creating a Template ref Inside Your Composable

Creating a ref that is going to be used to target the template within a composable and returning it is a little less problematic than the previous example, but still a side effect. If someone later on goes to look at your component and sees a :ref="" binding in the code and no ref being created directly in the setup function, they might be confused—thus forcing them to dig deeper to understand your code.

Vanilla Manipulation of the DOM

This is already a huge no-no in Vue in general, but even worse when hidden behind the door of a composition function. Avoid at all costs.

Modifying Global State (Vuex, Pinia, etc.) Within the Composable

This falls into the realm of the provide/inject problem. Your code will be a lot harder to follow, and any bugs will be harder to track since it isn’t immediately obvious where the modifications are taking place. This also will make your composable a lot let reusable since now it is tied to the specific needs of your SPA, should you wish to use it elsewhere.

Losing Prop Reactivity When Passing Down Data to a Composable

Another common problem I’ve seen is attempting to pass down data that is received from a prop to a composition function and losing the reactivity in the process. While this is not specifically a composition function at its core, but rather a lack of understanding or oversight of how reactive objects (like props) work, it is still worth mentioning in the context of this article.

Consider this example:

import useMyComposable from './MyComposable'

const props = defineProps({ myProp: { type: String, default: 'hi' } })

useMyComposable({ title: props.myProp })

At face value, this may seem like there is no problem, and it might even work (depending on how you’re using the title property inside of your composition function). But the reality is that if you were banking on that title being reactive to changes on myProp, it will not be the case.

The correct way to approach this is either by using a computed property that returns props.myProp so that reactivity is preserved, or by using toRefs to extract the ref from your props like so:

import useMyComposable from './MyComposable'
import { toRefs } from 'vue'

const props = defineProps({ myProp: { type: String, default: 'hi' } })
const { myProp } = toRefs(props)

useMyComposable({ title: myProp }) // reactive!

Creating a ‘Library’ Wrapped Inside a Composable

Another common misconception is that, now that we have composition functions, everything should be a composition function. Many times I have encountered composables that look like the following:

export default () => {
  const func1 = () => {}
  const func2 = () => {}
  return {

This composable provides no value in terms of component composition, and even worse provides an additional layer of complexity because now we have to both import it and call it in our setup method to get access to the functions.

This can be quickly solved by creating a library file that exports these functions instead, and just approach them as regular vanilla JavaScript.

export const func1 = () => {}
export const func2 = () => {}

Wrapping up

Don’t get discouraged if you’ve fallen into one of these pitfalls in the past, we’ve all been there! And also keep in mind that there is no absolute right or wrong—there might be times where you can justify the above, but it should be the exception, not the rule.

About the Author

Marina Mosti

Marina Mosti is a frontend web developer with over 18 years of experience in the field. She enjoys mentoring other women on JavaScript and her favorite framework, Vue, as well as writing articles and tutorials for the community. In her spare time, she enjoys playing bass, drums and video games.

Related Posts


Comments are disabled in preview mode.