How to access refs from app instance in Vue3 - vuejs2

In Vue2 it was possible to access refs from the vue instance like so:
vm.$refs.someRef
How can I achieve this in Vue3? Access the refs from outside the app instance i.e from js code.

If you are using options API, it's the same. If you want to use composition API, you pass a ref to the template. It gets a little confusing because there are two different refs one is the attribute in the template (ref="myref") and the other is the function const myref = ref(null)
When used in the template, the ref value gets updated and can be then accessed via myref.value
from https://gitlab.com/mt55/maintegrity-fim-web-interface-server/-/jobs/3293032688/artifacts/download
<script setup>
import { ref, onMounted } from 'vue'
// declare a ref to hold the element reference
// the name must match template ref value
const input = ref(null)
onMounted(() => {
input.value.focus()
})
</script>
<template>
<input ref="input" />
</template>
If the ref is needed from outside of the app, it can be accessed through the instance with:
const app=createApp(App)
app._instance?.refs
however that only works if the ref is in the App component. For every other component, while the ref is available somewhere in the app object, traversing through the structure is much more complicated.

Related

Vue3 Reactivity in script setup for translation

I am adding some DOM elements in the script setup side but I want the messages to change when I change the language. I am using vue-i18n plugin. It's easy to do it in the template section because I can basically use the useI18n().t method but how can I do this in the script setup section. Using the useI18n().t method doesn't ensure reactivity.
Example Code:
$(".time")[0].innerHTML = `
<div>0<span>${useI18n().t("details.hour")}</span></div>
<div>0<span>${useI18n().t("details.minute")}</span></div>
<div>0<span>${useI18n().t("details.second")}</span></div>
`
Manipulating DOM directly inside the script leads to inconsistence in your app, you should drive your component by different reactive data to achieve your goal.
In your current situation try to define a computed property based on the translation then render it inside the template based on its different properties :
<script setup>
const {t} =useI18n()
const time = computed(()=>{
return {
hour:t(""details.hour"),
minute:t(""details.minute"),
second:t(""details.second"),
}
})
</script>
<template>
<div class="time">
<div>0<span>{{time.hour}}</span></div>
<div>0<span>{{time.minute}}</span></div>
<div>0<span>{{time.second}}</span></div>
</div>
</template>

Vue 3 Mutate Composition API state from external script

Forewords
In Option API, I was able to directly mutate instance data properties without losing any of reactivity. As described in here.
If you ask why, well not everything is written in Vue and there're cases where external JS libraries have to change certain value inside Vue instance.
For example:
document.app = createApp({
components: {
MyComponent, //MyComponent is a Option API
}
})
//Somewhere else
<MyComponent ref="foo"/>
Then component state can be mutated as follow:
//Console:
document.app.$refs.foo.$data.message = "Hello world"
With the help of ref, regardless of component hiarchy, the state mutating process is kept simple as that.
Question
Now in Composition API, I want to achieve the same thing, with setup script if it's possible.
When I do console.log(document.app.$refs), I just only get undefined as returned result.
So let's say I have MyComponent:
<template>
{{message}}
<template>
<script setup>
const message = ref('Hello world');
</script>
How to mutate this child component state from external script? And via a ref preferably, if it's easier
Refs that are exposed from setup function are automatically unwrapped, so a ref can't be changed as a property on component instance.
In order for a ref to be exposed to the outside, this needs to be explicitly done:
setup(props, ctx) {
const message = ref('Hello world');
ctx.expose({ message });
return { message };
}
This is different in case of script setup because variables are exposed on a template but not component instance. As the documentation states:
An exception here is that components using are private by default: a parent component referencing a child component using won't be able to access anything unless the child component chooses to expose a public interface using the defineExpose macro
It should be:
<script setup>
...
const message = ref('Hello world');
defineExpose({ message });
</script>

Ref not working on custom component in Vue3

I'm using Vue3 with the composition API. In a form-component I put ref's on each field (child-component).
For some reason the ref's of the custom components are different from ref's for Quasar components.
When I console.log a ref to a custom component I get this in DevTools:
Proxy {__v_skip: true}
(without any properties in Target)
while a ref to a Quasar components gives this :
Proxy {…}
(with all properties of the component in Target)
For this reason I can't use the ref to access properties or methods of these child components.
I have no idea what __v_skip even means.
My custom components are defined with script setup, could that be a reason?
Any idea how to fix this?
UPDATE
If I use defineExpose in the child components for the properties and methods I want to access from outside with a ref, it does work. Not really handy though, since these components have lots of props.
Seem likes currently you cannot access the custom component by ref, if your component is written by Composition API (<script setup>). But you can try the way I mention underneath.
In the Vue 3 doc, there are some lines mentioned this behavior:
An exception here is that components using <script setup> are private
by default: a parent component referencing a child component using
<script setup> won't be able to access anything unless the child
component chooses to expose a public interface using the defineExpose
macro
Read more here: Vue 3 - Ref on Component
That means if you want to access anything from the custom component, your component has to expose that information. I think it's because in Vue 3 you don't need to have root component anymore, so if you define a ref, Vue does not know what the component you want to ref to.
But...
You can try to use yourRef.value.$el, maybe it will help.
Example:
// Parent.vue
<template>
<Child ref="childRef">
</template>
<script setup lang="ts">
// Import things...
const childRef = ref<InstanceType<typeof Child> | null>(null);
onMounted(() => {
console.log(childRef.value.$el);
});
</script>

Find nearest parent Vue component of template ref (Vue 3)

When a Vue template ref is mounted, I want to get the nearest parent Vue component. This should be generic and work for any template ref so I've put it in a composition function (but that's just an implementation detail).
I had this working but my implementation used elem.__vueParentComponent while iteratively searching an element's ancestors. While reading the Vue source code I saw __vueParentComponent was only enabled for dev mode or if dev tools is enabled in production. Thus, I don't want to rely on that flag being enabled.
I thought this might be possible using vnodes but this isn't easily google-able. Here's an example of what I'm trying to do:
function useNearestParentInstance(templateRef) {
function getNearestParentInstance(el) {
// code here
}
onMounted(() => {
const el = templateRef.value;
const instance = getNearestParentInstance(el);
// do something with instance
});
}
<template>
<div>
<SomeComponent>
<div>
<div ref="myElem"></div>
</div>
</SomeComponent>
</div>
</template>
<script>
export default {
setup() {
const myElem = ref();
// nearest would be SomeComponent's instance in this case
useNearestParentInstance(myElem);
...
}
}
</script>
If you want the nearest vue parent you can simply use
ref().$parent // Not sure if syntax is same in vue3
ref().$parent will get the first vuecomponent that is the parent of the ref that you placed.

Access the component name from a Vue directive

I want to create a custom Vue directive that lets me select components on my page which I want to hydrate. In other words, this is what I want to archive
I render my Vue app on the server (ssr)
I attach a directive to some components, like this:
<template>
<div v-hydrate #click="do-something"> I will be hydrated</div>
</template>
I send my code to the client and only those components that have the v-hydrate property will be hydrated (as root elements) on the client.
I want to achieve this roughly this way:
I will create a directives that marks and remembers components:
import Vue from "vue";
Vue.directive("hydrate", {
inserted: function(el, binding, vnode) {
el.setAttribute("data-hydration-component", vnode.component.name);
}
});
My idea is that in my inserted method write a data-attribute to the server-rendered element that I can read out in the client and then hydrate my component with.
Now I have 2 questions:
Is that a feasible approach
How do I get the component name in el.setAttribute? vnode.component.name is just dummy code and does not exist this way.
PS: If you want to know why I only want to hydrate parts of my website: It's ads. They mess with the DOM which breaks Vue.
I could figure it out:
import Vue from "vue";
Vue.directive("hydrate", {
inserted: function(el, binding, vnode) {
console.log(vnode.context.$options.name); // the component's name
}
});
I couldn't get the name of my single file components using the previously posted solution, so I had a look at the source code of vue devtools that always manages to find the name. Here's how they do it:
export function getComponentName (options) {
const name = options.name || options._componentTag
if (name) {
return name
}
const file = options.__file // injected by vue-loader
if (file) {
return classify(basename(file, '.vue'))
}
}
where options === $vm.$options