Search code examples
javascripttypescriptvue.jsvuejs3pinia

Vue Provide & Inject undefined on component load


Situation

I am trying to use the Options API for the Provide & Inject capabilities of Vue, so that the header.vue component can provide data to the container-nav.vue component (see component hierarcy). However, when the data is injected, it is undefined on initial load.

Current Component Hierarchy:

  • header.vue > header-nav-menu.vue > container-nav.vue

Attempt 1 - header.vue (grandparent)

In this component, I am fetching the following string '/' inside Provide() {}, from pinia (storeCommon).

import { defineComponent } from 'vue'
import parentStore from '@/store'

export default defineComponent({
    setup() {
        // Should not have access to this.navHome -> undefined (loads before options API)
        console.log("1. Calling setup in Header")

        const storeCommon = parentStore()
        return { storeCommon }
    },
    beforeCreate() {
        // Should not have access to this.navHome -> undefined (loads before options API)
        console.log("2. Calling beforeCreate in Header")
    },
    provide() {
        console.log("3. Calling provide in Header")
        return {
            navHome: this.storeCommon.textualData[0]?.navigation.links.home
        }
    },
    created() {
        // Should have access to this.navHome -> '/' (loads after options API)
        console.log("9. Calling beforeCreate in Container Nav: ", this.$refs.navHome)
    },
    mounted() {
        // Should have access to this.navHome -> '/' (loads after options API)
        console.log("8. Calling beforeCreate in Container Nav: ", this.$refs.navHome)
    }
})

Attempt 1 - container-nav.vue (child)

In this component, I am injecting navHome that was provided by the header.vue component.

import { defineComponent } from 'vue'

export default defineComponent({
    inject: [ 'navHome' ],
    setup() {
        // Should not have access to this.navHome -> undefined (loads before options API)
        console.log("4. Calling setup in Container Nav")
    },
    beforeCreate() {
        // Should not have access to this.navHome -> undefined (loads before options API)
        console.log("5. Calling beforeCreate in Container Nav")
    },
    created() {
        // Should have access to this.navHome -> '/' (loads after options API)
        console.log("6. Calling beforeCreate in Container Nav: ", this.$refs.navHome)
    },
    mounted() {
        // Should have access to this.navHome -> '/' (loads after options API)
        console.log("7. Calling beforeCreate in Container Nav: ", this.$refs.navHome)
    }
})

Attempt 1 - testing

Running the following code with the console.logs produces the following result:

1. Calling setup in Header                                
2. Calling beforeCreate in Header: undefined              -> Correct
3. Calling Provide in Header
4. Calling setup in Container Nav
5. Calling beforeCreate in Container Nav: undefined       -> Correct
6. Calling created in Container Nav: undefined            -> Incorrect (should be '/')
7. Calling mounted in Container Nav: undefined            -> Incorrect (should be '/')
8. Calling mounted in Header: undefined                   -> Incorrect (should be '/')
9. Calling created in Header: undefined                   -> Incorrect (should be '/')

Attempt 2 - header.vue (grandparent)

Using the same code as before & Options API, apart from using the setup with provide.

import { defineComponent, provide } from 'vue'
import parentStore from '@/store'

setup() {
    // 1. Tried this way
    const navLinkHome = parentStore().getTextualData[0]?.navigation.links.home
    const navHome = provide('navHome', navLinkHome)

    // 2. Tried this way
    const navHome = provide('navHome', parentStore().getTextualData[0]?.navigation.links.home)

    return {
        // 1 & 2
        navHome
        
        // And also this way
        navHome: provide('navHome', parentStore().getTextualData[0]?.navigation.links.home)
    }
}

Attempt 2 - container-nav.vue (child)

import { defineComponent, inject } from 'vue'

setup() {
    const navHome = inject('navHome')

    return {
        navHome

        // Tried this too, but same as above just longer
        navHome: navHome
    }
}

Attempt 2 - testing

Running the following code with the console.logs produces the following result:

1. Calling setup in Header                                
2. Calling beforeCreate in Header: undefined              -> Correct
3. Calling setup in Container Nav
4. Calling beforeCreate in Container Nav: undefined       -> Correct
5. Calling created in Container Nav: undefined            -> Incorrect (should be '/')
6. Calling mounted in Container Nav: undefined            -> Incorrect (should be '/')
7. Calling mounted in Header: undefined                   -> Incorrect (should be '/')
8. Calling created in Header: undefined                   -> Incorrect (should be '/')

Confusions

  • First of all, I noticed when using the provide() {} Options API within header.vue, and trying to reference navHome. I could not use this.navHome inside the created or mounted methods. I would get an error saying that it did not exist on type CreateComponentPublicInstance. To combat this problem, I instead used this.$refs.navHome - not even sure if this is the right thing to do.

  • Secondly, when using inject: [ 'navHome' ] inside the container-nav.vue component, I could not access it using this.navHome. Again, I could only access it using this.$refs.navHome.

HOWEVER.

  • When using Provide inside the setup method of header.vue and Inject inside the setup method of container-nav.vue, I could access navHome using this.navHome. Not sure why it does this.

Solution

    • You need to ensure that parentStore().getTextualData[0]?.navigation.links.home has the value "/" at the time the parent provides it because it is not a reactive object
    • Or you can provide a reactive object like that:
    const navLinkHome = computed(()=> parentStore().getTextualData[0]?.navigation.links.home)
    const navHome = provide('navHome', navLinkHome)
    
    • this.$refs.navHome is the wrong way, this.navHome should work. However, I recommend you use the composition API style
    • I'm curious that, you already have the pinia store, why do you need to use provide/inject at all?