vue ref on custom child component not working

Viewed 4711

I've been googling for hours, reading countless vue docs and relevant posts on stackoverflow without luck.

I need to access an internal property of a component, because that component is nothing but a wrapper for a vanilla component which I need access to in the parent.

I am trying to create a ref which is unique and accessible only inside the parent (App) component, and my understanding is that I should do that inside setup method like so: const myRef = ref(null)

But alas, myRef.value is forever null

codesandbox demo

Entry file:

import { createApp } from 'vue'
import App from "./App.vue";

createApp(App).mount('#app')

Parent App.vue:

<template>
    <Foo ref="myRef"/>
</template>

<script>
import { onMounted, ref } from 'vue'
import Foo from "./components/Foo.vue"

export default {
  name: "App",
  components: { Foo },

  setup(props) {
    const myRef = ref(null)

    onMounted(()=>{
      console.log(111,  myRef.value)
    })

    return {
      myRef
    }
  }
}
</script>

Child component Foo.vue:

<template>
  <div>xxx</div>
</template>

<script>
export default {
  name: "Foo",
  mounted() {
    this.test = 123
  }
}
</script>

if I place ref="myRef" on the <h1> it works, but I need it to work on my custom component so I could access things there...

Now I don't know jack**** about vue, and there's a TON of very conflicting confusing information out there.. I am using version v3 in my demo and the App component is authored in that manner (from what little I could understand from the insanity out there) but my component tagify.vue is authored in v2 fashion - for wider audience usage.


UPDATE - after lots of wasted hours

it's codesandbox console bug! it's wrongly displaying it as null I trusted their console too much not to open the browser's one (due to performance reasons as it greatly slows my computer on that specific website).

3 Answers

I ran into the same issue and resolved it on Vue Discord server. This is how you do it in Vue 3, with composition API:

App.vue:

<template>
  <v-header @get-started="scrollToTable" />
  <v-table ref="tableRef" />
</template>

<script setup>
import VTable from "./components/Table.vue";

import { ref } from "vue";

const tableRef = ref(null);

const scrollToTable = () => {
  tableRef.value.getRef()?.scrollIntoView({ behavior: "smooth" });
};
</script>

Table.vue:

<template>
  <table ref="root">
    <!-- some stuff here -->
  </table>
</template>

<script>
import { ref } from "vue";

export default {
  setup() {
    const root = ref(null);
    const getRef = () => root.value;

    return { root, getRef };
  },
};
</script>

As for the parent component I think the main problem is that currently on Vue 2.x you cannot put 2 direct child elements inside the template of the Vue component. *** You actually can, but only if you'll use JSX render function instead of the <template></template> block.

Second, you shouldn't use x.value inside the template, but only use the x (It's handled inside the vue-loader). So, replace :value="tagifyStuff.value" to :value="tagifyStuff".

as for the cold component The v-model won't work on the props.value, because data that comes from can't be mutated.

It's not the "vue way" to pass a function in props for actions, such as "onClick". Your component should use the $emit() method to dispatch an event, and the parent component should subscribe to this event using @my-event="doWhatever"

It's better to use your css from the <style> tag. you can also write <style src="@yaireo/tagify/dist/tagify.css"/> if you insist using a separated file.

BTW, if you want to use the Tagify instance from the parent, why won't you just emit it? you can write:

mounted() {
    this.tagify = new Tagify(this.$el, this.settings);
    this.$emit('ready', this.tagify);
}

and then use it in parent like that:

<template><my-child @ready="tagifyIsReady"/></template>
<script>
export default {
  setup() {
    let tagify;
    return: {
      tagifyIsReady: (tagifyInstance) => tagify = tagifyInstance
    }
  }
}
</script>

<template>
    <Foo ref="myRef"/>
</template>

<script>
import { ref, watch } from 'vue'
import Foo from "./components/Foo.vue"

export default {
  name: "App",
  components: { Foo },

  setup(props) {
    const myRef = ref(null)
    
    watch(myRef, newValue => {
       console.log(111,  newValue)
    })
    
    return {
      myRef
    }
  }
}
</script>

Related