How to use vue 3 composition API properly? Many instances of useSomething() or passing down objects/functions in props?

I have a hook (is that the right name?) that looks like this:

import { ref } from "vue"

const useImportantStuff = () => {
    const thingA = ref(0);    
    const thingB = ref("");    

    const importantFunction = () => {
        console.log("do something");
    }
    return { thingA, thingB, importantFunction };
}

export default useImportantStuff;

A parent-component uses thingA and thingB from the above hook.
A child-component needs to have access to function importantFunction.

Version 1:
Should parent-component use the useImportantStuff hook and pass down the importantFunction to child-component in props?

Version 2:
Or should both components call their own useImportantStuff hooks and get the necessary objects without using any props?

I have read that passing down functions in props is an anti-pattern in vue, but is it still true in vue 3 composition API? Should I somehow use events?

Version 1:
parent-component.vue:

<template>
    <div>{{thingA}} and {{thingB}}</div>
    <child-component :importantFunction="importantFunction"/>
</template>

<script>
import useImportantStuff from "@/path"
import ChildComponent from "@/components"

export default{
    components: {
        ChildComponent
    },
    setup(){
        const { thingA, thingB, importantFunction } = useImportantStuff();

        return { thingA, thingB, importantFunction };
    }
}
</script>

child-component.vue:

<template>    
    <button @click="importantFunction()"/>    
</template>
<script>
export default {
    props: {
        importantFunction: Function
    }
}
</script>

Version 2:
parent-component.vue:

<template>
    <div>{{thingA}} and {{thingB}}</div>
    <child-component/>
</template>

<script>
import useImportantStuff from "@/path"
import ChildComponent from "@/components"

export default{
    components: {
        ChildComponent
    },
    setup(){
        const { thingA, thingB } = useImportantStuff();

        return { thingA, thingB };
    }
}
</script>

child-component.vue:

<template>    
    <button @click="importantFunction()"/>    
</template>

<script>
import useImportantStuff from "@/path"

export default {
    setup() {
        const { importantFunction } = useImportantStuff();

        return { importantFunction }
    }
}
</script>

I think the term is “composable” (“hook” is more React). You can pass things down as props if you want, it really just depends on what you’re trying to do. I would avoid props if you can just import some shared composable/module though.

However, keep in mind if you import that composable in multiple components, they won’t share the same data. Each component will get its own, separate version of thingA, thingB, and importantStuff. This is because all your stuff is defined inside useImportantStuff(), so calling it multiple times creates new data each time. If you want stuff to be shared, you need to define them globally. For example:

import { ref } from "vue"

export const thingAGlobal = ref(0)
export const importantFunctionGlobal = () => {
  console.log("this will be shared");
}

export default function useImportantStuff() {
  const thingB = ref("")
  const importantFunction = () => { ... }
  return {thingB, importantFunction}
}
1 Like

That specifically refers to callback functions. Other types of function, such as formatting functions, can’t be replaced using events and are often passed using props.

The example code is a bit too generic. Without knowing what the relationship is between the components, the data and the function it’s difficult to know what the correct approach would be.

Given the information provided, I would say yes, though it would depend on precisely what importantFunction does. If the child needs access to the function’s return value then events aren’t necessarily suitable.

The child should $emit a suitable event whenever it wants importantFunction to be called and the parent would then listen for that event and make the call to importantFunction.

1 Like

Thanks for the answers. (can only mark one as solution :/)

My useImportantStuff composable contains a bunch of functions with fetch api calls. Functions like getItems(), saveItem(id), deleteItem(id), so my components don’t really need to share any data.

The parent-component is something like a list of menu items and the child-component is a list of drinks. The child components needs to call menuItemsApi.saveRecommendedDrink(drinkId)

So I guess my importantFunction or saveRecommendedDrink is a callback and i shouldn’t use props to pass it.

I think I’ll settle on using events, although importing the function out of a composable also seems fine.