vue.jsvuejs3

Passing template Ref with Provide Inject


I am trying to access a template ref from a deeply nested children. I have three components, SecondLayer.vue, HelloWorld.vue and Calendar.vue. I want to provide childrenRef from SecondLayer.vue and access it in Calendar.vue via inject.

In SecondLayer.vue:

<template>
    <div ref="childrenRef">
        This is from the second layer HEADER
        <slot name="header"></slot>
        This is the from second layer MAIN. The price is: {{  test }}
        <slot name="main"></slot>
    </div>
</template>
<script setup>

import { inject, provide, ref, onMounted } from "vue";

const test = inject("price");
const childrenRef = ref(null);
const test1 = ref("TEST1");
console.log(childrenRef.value);
// provide("childrenRef", childrenRef.value);
provide("test1", test1.value);

onMounted(() => {
    console.log(childrenRef.value);
    provide("childrenRef", childrenRef.value);
});

console.log(test);
</script>

In HelloWorld.vue:

<template>
  <div class="greetings">
    <second-layer>
      <template #header>
        <button>A button for the header</button>
      </template>

      <template #main>
        <button>A button for main</button>
        <Calendar/>
      </template>
    </second-layer>
    <h1 class="green">{{ msg }}</h1>
    <h3>
      You’ve successfully created a project with
      <a href="https://vite.dev/" target="_blank" rel="noopener">Vite</a> +
      <a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next?
    </h3>
  </div>
</template>

In Calendar.vue:

const childrenRef: any = inject("childrenRef");
const test1: any = inject("test1");
watch(childrenRef, newVal => {
    console.log(newVal);
})

onMounted( async () => {
    selectedDaysInMonth.value = daysInMonth(selectedYear, selectedMonth);
    await nextTick();
    console.log(test1);
    console.log(childrenRef);
})

In the onMounted of SecondLayer I can see children.ref showing the div. In the onMounted of Calendar I can see test1 being logged as TEST1 but childrenRef is logged as null. This is where I don't understand. I've ensured it's provided when the SecondLayer component is mounted and that's when I provide childrenRef, why is it that the Calendar component sees it as null?

Can someone explain what I am doing wrong or misunderstanding?


Solution

  • The problem that you provide an unwrapped ref value which is undefined at the moment of calling provide.

    Rather provide the ref itself:

    provide("childrenRef", childrenRef);
    

    Then you will be inject this ref and use it as a ref. Providing the DIV element wouldn't work since it's not available at the time the setup function is called. Moreover Vue can re-render and recreate HTML with another DIV for various reasons (introduced later for example) and you will have an invalid (old) DIV. So use a reference.

    Playground

    In the Calendar.vue you get your DIV:

    enter image description here