Why is ref in onMounted null in vue3? - vuejs3

It is normal to write it in setup, but it prints a null in onMounted;
What is the reason? Isn't onMounted between the two executed later, and the dom has been loaded, why can it be obtained in the setup instead?
<script setup lang="ts">
const refDom = ref<any>(null);
console.log(1, refDom.value); //Write this to get
</script>
<script setup lang="ts">
onMounted(()=>{
const refDom = ref<any>(null);
console.log(2, refDom.value); // print out here is null
})
</script>

ref should be written at the top level
<script setup lang="ts">
const refDom = ref<any>(null);
onMounted(()=>{
console.log(2, refDom.value);
})
</script>

Related

How to load GTM with NextJS Script component?

I recently came across the script component in NextJS (https://nextjs.org/docs/basic-features/script)
I wanted to know if it's possible to load Google tag manager using this component.
You can add the following <Script> tags within your _app.tsx file.
// pages/_app.tsx
export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
const getLayout = Component.getLayout ?? ((page) => page)
return getLayout(
<>
<Script id="google-site-tag"
strategy='lazyOnload'
src={`https://www.googletagmanager.com/gtag/js?id=${GOOGLE_TAG_ID}`}
/>
<Script id="google-analytics" strategy='lazyOnload'>
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${GOOGLE_TAG_ID}');
`}
</Script>
<Component {...pageProps} />
</>
)
}

Using querySelector() in vue Compostion API

I'm new to Vue composition API and need to use the Document method querySelector. However, it is not working as expected. If i write
<nav class="home-menu pure-menu pure-menu-horizontal pure-menu-fixed">
<script setup>
import { ref } from "vue";
const observer = new IntersectionObserver(handleIntersection);
const target = document.querySelector(".home-menu");
observer.observe(target);
console.log(target);
target is null. Reading docs I see the ref attribuute and if I
<nav ref="navbar" class="home-menu pure-menu pure-menu-horizontal pure-menu-fixed">
<script setup>
import { ref } from "vue";
const target = ref("navbar");
console.log(target);
I do console an object. Is ref the way that you get a DOM element in composition API? Can I now use target in my observer object? Is it equivalent to querySelector? I tried this
import { ref } from "vue";
const observer = new IntersectionObserver(handleIntersection);
const target = ref("navbar");
observer.observe(target);
but got error
Uncaught TypeError: IntersectionObserver.observe: Argument 1 does not implement interface Element.
Thanks.
The reason document.querySelector is returning null is that the DOM does not contain that element yet.
The script setup runs when the component is created, so the component's DOM has not been created yet.
You can use the onMounted lifecycle hook to run code when the component has been mounted. I created a playground to demonstrate this.
From the Lifecycle docs:
For example, the onMounted hook can be used to run code after the component has finished the initial rendering and created the DOM nodes
There are then two approaches to achieve what you want. You can use continue to use querySelector, or you can use template refs.
Personally, I use template refs for static elements (such as navbars, etc.) and querySelector for dynamic selections.
Using Template Refs
A template ref is a regular Vue ref, but by connecting it to an element or child component via the ref attribute you can obtain a direct reference to that element or component.
If you connected it to an element, the value will be that element; if you connected it to a child component, the value will be that component's component instance; if nothing is connected to it, the value will be null.
Steps
Create a ref:
const navbar = ref(null);
This will be used to refer to the element.
Connect the template ref to the element by setting the ref attribute on the element to the name you used for the template ref:
<nav ref="navbar" ...>
Paraphrasing the Template Refs docs:
ref is a special attribute. It allows us to obtain a direct reference to a specific DOM element or child component instance after it's mounted.
Connect the observer when the component is mounted:
onMounted(() => {
observer.observe(navbar.value);
})
Paraphrasing the docs again:
Note that you can only access the ref after the component is mounted. If you try to access navbar before then, it will be null. This is because the element doesn't exist until after the first render!
Optionally (see below), disconnect the observer when the component is being unmounted:
onBeforeUnmount(() => {
observer.disconnect();
})
Note that I don't believe this is technically necessary, as the observer should be garbage collected when the component is destroyed.
You can fiddle around with this experiment I did in the SFC playground, trying to create a memory leak.
Code Example
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
const el = ref(null);
const observer = new IntersectionObserver((entries, observer) => {
console.log(entries)
});
onMounted(() => {
observer.observe(el.value)
})
// Probably optional
onUnmounted(() => {
observer.disconnect()
})
</script>
<template>
<div ref="el">
I'm the target!
</div>
</template>
Using querySelector
Alternatively, you can still use querySelector. The same lifecycle considerations apply.
<nav class="home-menu ...">
onMounted(() => {
const target = document.querySelector(".home-menu");
observer.observe(target);
})
Code Example
<script setup>
import { onMounted, onUnmounted } from "vue";
const observer = new IntersectionObserver((entries, observer) => {
console.log(entries)
});
onMounted(() => {
const target = document.querySelector(".target");
observer.observe(target);
})
// Probably optional
onUnmounted(() => {
observer.disconnect()
})
</script>
<template>
<div class="target">
I'm the target!
</div>
</template>
Other Docs
This is the diagram from the Lifecycle docs:
Side Notes
The reason console.log(target) did log an object is because a Vue ref is an object. The actual value is accessed via the value property.
The technical reason for this is that Vue can then detect when that property is accessed, and do its reactivity magic; including when it was a complete reassignment of the value.
You can use the ref approach, calling the variable with the same name you declared the ref in the template:
const navbar = ref(null);
However, you should await the component to be mounted to observe:
onMounted(() => {
observer.observe(target);
})
remember also to disconnect it when you unmount the component:
onBeforeUnmount(() => {
observer.disconnect();
})

create vue 3 component on the fly based on a object using resolveDynamicComponent

My goal is to let users create vue components based on objects using resolveDynamicComponent. While refresh page, render and data binding goes well, but after that, it gets some error and makes the rendered item disappear.
<template>
<abc/>
</template>
<script setup>
import {resolveDynamicComponent} from 'vue'
const abc = resolveDynamicComponent(
{
name: 'abc',
setup: () => {
const test = 'Test'
},
template: '<div>{{test}}</div>'
}
)
</script>
Error
Hydration completed but contains mismatches.
hydrate # runtime-core.esm-bundler.js:3136

global pub-sub/event-handling in ractive

I'm trying to determine the best way to establish cross-component-communication. My first thought was to use ractive.fire with a wildcard, but that doesn't seem to work. Am I trying to mis-use ractive.fire? What would be the suggested way for doing cross-component-communication with ractive?
Ractive.components.pubSub = Ractive.extend({
oninit() {
this.on('*.customEvent', () => alert('pub sub got your custom event!'))
}
})
Ractive.components.something = Ractive.extend({
template: '#something'
})
let ractive = new Ractive({
target: 'body',
template: '#app'
})
<script src="https://cdn.jsdelivr.net/npm/ractive#0.10.3/ractive.js"></script>
<script id="app" type="text/ractive">
<pubSub />
<something />
</script>
<script id="something" type="text/ractive">
<button on-click="#.fire('customEvent')">Fire Custom Event</button>
</script>
Ractive doesn't prescribe a convention for data sharing/cross-component communication. However, it does give you the facilities to do it. A common practice I've seen is to create a "dummy instance" and use its ractive.fire() and ractive.on() methods.
// The dummy instance, make it visible to components.
const pubsub = Ractive()
const SourceComponent = Ractive.extend({
template: '<button click="send()">Click me</button>',
send(){
pubsub.fire('message')
}
})
const ListeningComponent = Ractive.extend({
onInit(){
pubsub.on('message', () => {
console.log('called')
})
}
})
Alternatively, if all you want is to share state across all components, modify them anywhere you want, and have everyone re-render on change, you can put that state in #shared.

How to set an API key when using Google's script loader?

I registered my project and generated a browser key at https://code.google.com/apis/console/.
Now how do I use that key when using the Google Script Loader?
I've been doing this, which works (with or without the key parameter), but even after several weeks the API console shows no requests:
<script src=//www.google.com/jsapi?key=my_key"></script>
<script>
google.load('maps', '3.10', { other_params : 'libraries=places&sensor=false', callback : ... })
</script>
The key is useless for the jsapi, you must append it to other_params:
<script>
google.load('maps', '3', {
other_params: 'key=my_key',
callback: function(){}
});
</script>
When using charts/loader you have to do something like this:
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
<script type="text/javascript">
google.charts.load('current', {
'packages':['geochart'],
// Note: you will need to get a mapsApiKey for your project.
// See: https://developers.google.com/chart/interactive/docs/basic_load_libs#load-settings
'mapsApiKey': 'AIzaSyD-9tSrke72PouQMnMX-a7eZSW0jkFMBWY'
});
google.charts.setOnLoadCallback(drawRegionsMap);
...
</script>
Note the mapsApiKey property.
As described in https://developers.google.com/chart/interactive/docs/gallery/geochart

Resources