I have a custom component that basically is a v-btn component with specific style. When I'm using it inside v-menu activator with conditional based on display breakpoint, the custom component does not display on the screen. But if I use regular v-btn the button displays properly based on the display breakpoint. What am I doing wrong here?
https://codepen.io/jgunawan-dc/pen/XWzJqRy?editors=1010
<div id="app">
<v-app id="inspire">
<div >
<v-menu offset-y>
<template v-slot:activator="{ on, attrs }">
<global-custom-button
v-if="$vuetify.breakpoint.mdAndDown"
v-bind="attrs"
v-on="on"
>
Show on medium and lower
</global-custom-button>
<v-btn v-else
color="primary"
dark
v-bind="attrs"
v-on="on"
>
Dropdown
</v-btn>
</template>
<v-list>
<v-list-item
v-for="(item, index) in items"
:key="index"
>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</div>
</v-app>
</div>
Vue.component('global-custom-button', {
template: '<v-btn outlined color="info" @click="$emit(\'click\', $event)"><slot></slot></v-btn>'
});
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
items: [
{ title: 'Click Me' },
{ title: 'Click Me' },
{ title: 'Click Me' },
{ title: 'Click Me 2' },
],
}),
})
CodePudding user response:
The are two errors arising from the above scenario:
Error message 1:
[Vue warn]: Error in nextTick: "NotFoundError: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node."
Error message 2:
DOMException: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.
This arises when Vue tries to insert an element before another one, but the element no longer exists in the DOM.
In your case, it seams that changing $vuetify.breakpoint.mdAndDown
from true to false or vice-versa cleans the global-custom-button
component or the Dropdown v-btn
.
One possible workaround would be to use v-show
instead of v-if
.
A caveat:
Note that v-show doesn’t support the element, nor does it work with v-else.
So this suggestion can work (you can change to fit your needs):
<global-custom-button
v-show="$vuetify.breakpoint.mdAndDown"
v-bind="attrs"
v-on="on"
>
Show on medium and lower
</global-custom-button>
<v-btn v-show="!$vuetify.breakpoint.mdAndDown"
color="primary"
dark
v-bind="attrs"
v-on="on"
>
Dropdown
</v-btn>
CodePudding user response:
For a quick fix, use v-show
as @Rotiken said.
As another solution you could also register two global components and show/hide it using extra condition
prop:
<v-menu offset-y>
<template v-slot:activator="{ on, attrs }">
<global-custom-button
:condition="$vuetify.breakpoint.mdAndDown"
v-bind="attrs"
v-on="on"
>
Show on medium and lower
</global-custom-button>
<global-custom-button-2
:condition="!$vuetify.breakpoint.mdAndDown"
v-bind="attrs"
v-on="on"
>
Dropdown
</global-custom-button-2>
</template>
...
</v-menu>
...
Vue.component('global-custom-button', {
template: '<v-btn v-if="condition" outlined color="info" @click="$emit(\'click\', $event)"><slot></slot></v-btn>',
props: ['condition']
});
Vue.component('global-custom-button-2', {
template: '<v-btn v-if="condition" color="primary" dark @click="$emit(\'click\', $event)"><slot></slot></v-btn>',
props: ['condition']
});
...
According to this CodePen, this also works.
Why this happens and why v-show
works fine with same template: I can't give an exact answer, but there are some assumptions.
Vue.js docs says that v-if
removes objects from DOM, whereas v-show
changes its display state in CSS, but keep it in DOM.
Since you are using v-if
in a vuetify v-menu
component, perhaps the component has some methods for updating the v-slot:activator
content that can conflict (and not performed at the same time) with conditional rendering using v-if
and vuetify display breakpoints.
If you are familiar with TypeScript, you can look into v-menu sources or into activatable mixin sources. Maybe here you will find the true reason for this behavior.
If you just want to avoid such problems, use v-show
in such cases.