Access dom element with vuejs and vuetify in v-tabs - vue.js

I have a div tag with an 'id="meet"' into v-tabs.
I need to access the node of this tag after I click a tab. I am using
let node=document.querySelector("#meet").
My problem is that it always returns "null". Here is the codepen: https://codepen.io/luizalves/pen/WNrepxz
What is wrong here?

There is no guarantee that after $nextTick you will see DOM element immediately.
<div id="meet"></div>
<button #click="onTest">test</button>
...
methods: {
onTest () {
let node=document.querySelector("#meet");
console.log('meet here',node)
},
After you clicked test you'll see in a console:
"meet here" "<div id='meet'></div>"
You can try to extract this div with an unique id into a separate component and inside it you can use mounted hook to know that it exists in DOM

Of course, according to vuetify docs, you may add eager prop to v-tab-item component, and that's it, but...possibly you are doing something wrong.
It's not a good idea to always add this prop because you are losing one of the vuetify 2.X advantages - lazy loading. This may lead to big performance problems if there are many elements on the tab.
Maybe it would be better if you will work directly with reactive variables (like variables in v-model directive), not DOM objects.

Related

How to get rid of Mismatching childNodes vs. VNodes in NuxtJs [duplicate]

I am using Nuxt.js / Vuejs for my app, and I keep facing this error in different places:
The client-side rendered virtual DOM tree is not matching server-rendered content.
This is likely caused by incorrect HTML markup, for example nesting block-level elements inside <p>, or missing <tbody>.
Bailing hydration and performing full client-side render.
I would like to understand what is the best way to debug this error? Is their a way I can record/get the virtual DOM tree for client and server so I could compare and find where the error lies?
Mine is a large application and manually verifying is difficult.
Partial answer: with Chrome DevTools, you can localize the issue and see exactly what element caused the issue. Do the following (I did that with Nuxt 5.6.0 and Chrome 64.0.3282.186)
Show DevTools in Chrome (F12)
Load the page that causes "the client-side rendered virtual DOM tree..." warning.
Scroll to the warning in DevTools console.
Click at the source location hyperlink of the warning (in my case it was vue.runtime.esm.js:574).
Set a breakpoint there (left-clicking at line number in the source code browser).
Make the same warning to appear again. I'm not saying it is always possible, but in my case I simply reloaded the page. If there are many warnings, you can check the message by moving a mouse over msg variable.
When you found your message and stopped on a breakpoint, look at the call stack. Click one frame down to call to "patch" to open its source. Hover mouse over hydrate function call 4 lines above the execution line in patch. Hyperlink to the source of hydrate would open.
In the hydrate function, move about 15 lines from the start and set a breakpoint where false is returned after assertNodeMatch returned false. Set the breakpoint there and remove all other breakpoints.
Make the same warning to happen again. Now, when breakpoint is hit, execution should stop in the hydrate function. Switch to DevTools console and evaluate elm and then vnode. Here elm seem to be a server-rendered DOM element while vnode is a virtual DOM node. Elm is printed as HTML so you can figure out where the error happened.
For me this error happened cuz get Array list in AsyncData and rendered <tr> tags by v-for, i put v-for codes in <client-only> blocks and problem solved
This error can be really painfull to debug. In order to quickly get the element causing an issue edit node_modules/vue/dist/vue.esm.js and add the following lines :
// Search for this line:
function hydrate (elm, vnode, insertedVnodeQueue, inVPre) {
var i;
var tag = vnode.tag;
var data = vnode.data;
var children = vnode.children;
inVPre = inVPre || (data && data.pre);
vnode.elm = elm;
// Add the following lines:
console.log('elm', elm)
console.log('vnode', vnode)
console.log('inVpre', inVPre)
// ...
You will get in the console the failing node.
There are a lot of ways of fixing this issue, but most of them are not actual fixes, just hacky band-aids. To note a few:
wrap it into <client-only> tags, beware of some important details tho
using a v-show instead of a v-if
trying to hack some lifecycles
etc...
I highly recommend reading this gorgeous article written by Alexander Lichter
https://blog.lichter.io/posts/vue-hydration-error/
He'll explain you that you should diagnose why this happens and fix the actual issue.
Basically each time something is different from what was generated on the server and what is available when done hydrating on the client will cause this error.
Some of which are:
invalid HTML (having a block element inside of a <p>, same goes for an a tag nested into another, etc...)
3rd party scripts messing around with your components
different state on server vs client
any random is risky (new Date() for example)
any page related to authentication
I highly recommend reading the article to understand in Alexandre's own words how to handle this kind of issue. If you're in a hurry you could always use one band-aid fix but try to actually fix the issue for the best performance and to keep the code clean.
I had the same issue as of nuxt version 2.14.0 while implementing vue-particles package. The fix was to surround the tags with no-ssr and it fixed the issue.
EDIT:
Updated variant of the solution (if Nuxt version is above 2.9.0)
<client-only>
<vue-particles>
</vue-particles>
</client-only>
Old solution:
<no-ssr>
<vue-particles>
</vue-particles>
</no-ssr>
Thanks to budden73's answer, I did a little improvement on the debug process.
Open dev tool
click on the warn message, and click on the first line of the warn message, you will be directed to the Sources panel, with a file name vue.runtime.esm.js?xxxx
ctrl+f to search the above file for assertNodeMatch, not the function, but like:
if (process.env.NODE_ENV !== 'production') {
if (!assertNodeMatch(elm, vnode, inVPre)) {
return false
}
}
Add a break point at the line return false
Refresh the page, and the breakpoint will be triggered.
At the right side of the Sources panel, Under Scope->Local, click on the elm element, you will be directed back to the Elements panel.
The above element is the client side rendered element, compare with your code to see the difference.
If you can't find the source of the bug, the brutal way to fix it is using nuxt's <client-only> tag.
Another likely brutal way is described here. Add an isHydrate variable which default is false, set to true in mounted hook, and render the element after the variable set to true.
For Nuxt version above 2.10 it doesn't need to install nothing, just use the default component <client-only> as mentioned https://nuxtjs.org/api/components-client-only/.
Check the previous warning:
In "nuxt": "^2.12.2", You can spot the cause easily from the previous warning.
In my case:
Incorrect
<nuxt-link to="/game42day">
<a>Game For Today</a>
</nuxt-link>
Correct:
<nuxt-link to="/game42day">
Game For Today
</nuxt-link>
If you're rendering a component conditionally with v-if, then you have two options to solve the problem:
The first one is wrapping the element in <no-ssr></no-ssr> tag.
The second approach is replacing v-if with v-show, here is the link to Vue docs.
Turns out, in my case, I had HTML comment tags , which was causing this stupid, annoying error. Took me too long to figure it out but in case it helps someone.
In my case I had to change this:
<v-expansion-panel-header v-text="name" />
to this:
<v-expansion-panel-header>{{ name }}</v-expansion-panel-header>
I also get many errors due to this problem. I list two cases I often encounter, hope can help you.
With vuetify button, when you create a common component, you should use: <v-btn>{{text}}</v-btn>. Example:
<template>
<v-btn
:width="width"
:color="color"
:class="[rounded ? 'rounded-pill' : 'rounded-lg',textColor]"
v-on:click="onClick"
elevation="0"
:outlined="outlined"
:type="type"
:name="name"
:form="form"
:disabled="disabled"
v-bind="$attrs"
>{{ text }}</v-btn>
</template>
Don't use v-html with <p> tag.
Not use: <p v-html='html'></p>.
Use: <div v-html='html'></div>.
Besides, if you use <client-only></client-only>, this problem is definitely solved, but if you need to SEO page or show google ads, it is not good solution.
Ok this is going to sound silly. I tried a bunch of different solutions for about 15 mins such as restarting the server and deleting the .nuxt directory but I was too lazy to use #budden73's big brain solution. What ended up working for me was simply restarting my computer, give it a shot.
What I have found so far from observation is that when you are using third party packages like jQuery (specially), they sometimes inject html tags into the dom. So Vue/Nuxt looses track of the dom tree and starts complaining.
I was having the same problem and after a while I removed all jQuery and replaced jQuery functionality with Vuejs and those error were all gone.
See here for an example of how to deal with integrations (e.g. Google Analytics or FB Pixel) that modify the DOM. Basically create a plugin and exclude from SSR.
https://nuxtjs.org/faq/ga
What about:
extend (config, ctx) {
config.resolve.symlinks = false
}
See this [Vue warn]: The client-side rendered virtual DOM tree is not matching server-rendered content ( Nuxt / Vue / lerna monorepo )
Now that you found the code causing the problem, the first thing you should do is to verify that your markup (possibly coming from an API) is valid. Code like <p><p>Text</p></p> is not valid because a p element doesn’t allow other block elements (like a paragraph tag) inside.
Be aware, that tags are not allowed to have block level elements like <div> or <p> as children. These <span> tags are used default tag for Vue’s transitions though. You can change that though via <Transition tag="div">.
Check if have used any block-level element inside the inline element.
for example: inside , inside
If you have used an HTML table make sure you have used the tag
In my case, I changed my codes from
<p v-html="$md.render(post.content)"></p>
to
<p>{{ $md.render(post.content) }}</p>
In my case this problem was caused by markdownit module, I solved it by changing the html markup used with v-html. I was with <p> at the beginning and I ended with <div>.
I have some <p> in my v-html render (with $md.render()) so take care if you have same problems with different markups.

How to work with vue.js and window.getSelection()?

Here is an example from some tutorial:
<p id="p">Example: <i>italic</i> and <b>bold</b></p>
<script>
let range = new Range();
range.setStart(p.firstChild, 2);
range.setEnd(p.querySelector('b').firstChild, 3);
alert(range); // ample: italic and bol
// use this range for selection (explained later)
window.getSelection().addRange(range);
</script>
How to do this with vue?
Should I use query selectors too?
I am interested how to do this selection manipulation within contenteditable. I can use "ref" for contenteditable container but inner content with bold, italic etc. tags is dynamic and mark this code with refs:
... <b><i>some text</i></b> ...
isn't appropriate.
If you're using the Selection API to manipulate the DOM (not just creating ranges, but adding/removing/modifing/reordering DOM nodes), then you should be doing those manipulations on DOM elements not managed by Vue. You must not modify parts of the DOM managed by Vue because Vue won't know about those changes and it will get confused when trying to patch the DOM during the next render cycle. Vue "owns" the rendered DOM of its components.
This doesn't mean you can't use the Selection API and Vue together, it just means you need to be careful not to tinker in the DOM willy-nilly.
Should I use query selectors too? Using p.querySelector('b').firstChild isn't vue way but I don't know what should I use insted
This rule only applies if you want to get a reference to a DOM node rendered by Vue. If the container were content editable, then its contents would not be managed by Vue and you would have no way of referencing its contents without using querySelector and other DOM functions. Try not to get caught up with "am I doing it the Vue-way?" when what you are trying to do is inherently not Vue related anyway.

Vue force all children components to render

I’m using Vue & Vuetify to create my app. With vuetify I’m using v-expansion-panels to create an accordion style display. Each v-expansion-panel itself contains a custom component.
I have noticed these components are not created until the expansion panel is clicked for the first time. After that, using keep-alive allows all reactive properties and methods of the child component to be active (this is my desired behavior).
How can I force the child components to be created when the parent is created? This, any method triggered in the created() lifecycle hook of a child component should fire when the parent is created.
This Codepen is an example of the current behavior. Note: be sure to look at the console when you click the panel.
If you think about it, it actually makes sense to lazy load content of expansion panels since it is useless work if the user never opens them anyway. So probably the thing you try to accomplish has some better approach, but if you still like it then my advice is to find a way of programatically opening / closing the panel (as seen here) and quickly open it and close it when rendering parent component. In this way, you will have your child component created and the UI will remain the same.
A Vuetify solution should be achievable by adding the eager prop to a v-expansion-panel-content element in the Expansion Panel. This should force any components or content contained within the v-expansion-panel-content element to render on mounted.
<v-expansion-panels v-model="panels">
<v-expansion-panel>
<v-expansion-panel-content eager>
<custom-component />
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>

Default-expand-all doesn't work for q-tree? Vue.Js

I have to code a web application and the most important element is the q-tree. I'm already able to load and show data (passing an array called list), but I want that all nodes are expanded.
The vue.js examples of the official documentation show that you're be able to do this with the 'default-expand-all' attribute but this isn't working for me.
It only shows me the root node with an arrow, where I have to expand the children nodes manually.
<q-tree
:nodes="list"
:selected.sync="selected"
#update:selected="onSelectionChangedNode"
node-key="NodeNr"
label-key="NodeTxt"
default-expand-all
></q-tree>
Taking a cue from the accepted answer, I realised that the dom has already been created with the tree component on first render.
In my use case, I want to update the Tree when data comes back from the server.
So, I had to force it to re-render with the expanded functionality using:
this.$nextTick(function () {
this.$refs.nodes.expandAll();
})
The nextTick function will update the dom in the next window of execution, by which time the nodes will get expanded by calling the expandAll function.
And NB: For those confused by the astericks on the ref attribute or how to add it to the component, here goes:
<q-tree :nodes="list"
:selected.sync="selected"
#update:selected="onSelectionChangedNode"
node-key="NodeNr"
label-key="NodeTxt"
ref="nodes"
>
Solved my problem as following:
I have added a ref attribute to the QTree DOM Element which makes it possible to access predefined methods of QTree API.
<q-tree
:nodes="list"
:selected.sync="selected"
#update:selected="onSelectionChangedNode"
node-key="NodeNr"
label-key="NodeTxt"
**ref="nodes"**
>
The function I have been using is expandAll().
updated() {
this.$refs.nodes.expandAll();
}
The most important thing for me was, I had to find out which lifecycle hook was the right one for me. The update() hook was the one I was looking for.
The reason:
Called after a data change causes the virtual DOM to be re-rendered and
patched.
The component’s DOM will have been updated when this hook is called, so you
can perform DOM-dependent operations here.
The default-expand-all is only applied on the first rendering of that Component.
So if your Component renders when the nodes aren't assigned they wont expand if assigned afterwards.
https://v1.quasar-framework.org/vue-components/tree
You have to work with scoped slots and an expanded attribute if you dont have the nodes on first rendering.

How to render a vnode (slot of a vue component) and mount it on an element?

For example, here we have a dialog component.
...
<dialog>
<div>
{{data}}
</div>
</dialog>
...
As we known, due to the stack context, it's hard to make the dialog always be the topmost element.
So we need to mount the dialog element as a body's child.
I tried this in dialog's mounted hook.
var slot=this.$slots.default
var vm = new Vue({
render: ()=>slot
}
var div=document.createElement('div')
document.body.append(div)
vm.mount(div)
The result is: it mounted successfully.
However, when I changed the data, the mounted vm didn't change.
I wonder how to make it reactive or is there any other way to achieve this.
Now that I can't find any methods to solve this.
I have to read some open source ui framework's source code because I know they do put the dialog in the body's child level.
And I found that they do render the <slot>, but I can't find any code rendered in the corresponding position and I can't find any code they use to 'remove' it.
...
Then, I found a line of code, document.body.append(this.$el). I am stupid. I didn't realize that append will just remove the element from it's original position and append it to the new position util I re-read the whole code once again.
I did think about this way, just append $el to somewhere. But I am afraid that it'll break the component down.
So, the solution is:
just define a normal vue component
ship the $el or child element to anywhere you want using element.append etc. after the component mounted
no need to remove the element manually when component is destroyed if you just ship the $el, vue will take care of it.