Nuxt2/Vue2, checking a $slots sometime fails on rerender - vuejs2

I have a strange issue on a Nuxt2/Vue2 project.
I have a component with a slot named "image" conditionally displayed. The condition only checks "$slots.image". It works properly on the first render of the component, but if it have to rerender, sometimes the condition fail.
I fixed this issue by modifying the condition to checks also "$scopedSlots.image". But I don't understand what is happening.
Does someone can explain to me what is happening here ?
Here is the parent where my component is imported:
<template>
<div>
<nav-pane>
<template #subheader>
<nav-pane-sub-header>
<template #image>
<avatar />
</template>
</nav-pane-sub-header>
</template>
</nav-pane>
</div>
</template>
Here is my component 'NavPaneSubHeader' with the buggy condition:
<template>
<div>
<div>
<i v-if="icon" />
<slot v-else-if="$slots.image"/>
</div>
<slot />
</div>
</template>
Works fine with :
<slot v-else-if="$scopedSlots.image || $slots.image" />

Related

VeeValidate 3.0.0 : use ValidationProvider in parent component not working

im using VeeValidate 3.0.0 and nuxt 2.15.8 and i wonder what i am missing out.
Parent Component:
<template>
<ValidationProvider
v-slot="{errors}"
:rules="rules"
<form-checkbox-item
:option="option"
:is-special="isSpecial"
:is-required="sectionChildContent.label.mandatory"
:name="sectionChildContent.name"
:title="sectionChildContent.label.title" />
<form-errors v-if="showErrors" :errors="errors" />
</div>
</ValidationProvider>
</template>
Child Component:
<template>
<div>
<input
v-model="selected"
:value="option.value"
:name="name"
:required="isRequired"
:checked="isChecked"
type="checkbox">
</div>
</template>
(simplified code)
Is this possible? When i use ValidationProvider in my child component everything works fine.
Inject as proposed here in v 2. is not working:
https://vee-validate.logaretm.com/v2/concepts/injections.html#injecting-parent-validator

Vue transitioning effects disappear when inside a vue slot

I'm having a hard time getting a vue transition to work inside a slot. I should mention I've searched through numerous stackoverflow questions and I haven't seen one that matches my issue. All the ones I've seen so far are talking about <slot> inside the <transition>, eg: vue transition component doesn't work correctly, whereas for me it's the other way round.
There are three vue components, component1, component2 and component3.
component1 has something like:
<template>
<slot name="content">
<slot name="side">
<slot name="default">
</template>
component2 looks something like below:
<component1>
<template #content>Some stuff here</template>
<template #side>Some stuff here</template>
<template #default>
<component3/> #Component3 contains the transition stuff
</template>
</component1>
component3 contains the transition stuff:
<transition
:name="right ? 'slide-right': 'slide-left'"
#before-enter="$emit('before-enter')"
#after-leave="$emit('after-leave')"
>
<div
v-show="$attrs.value"
v-bind="$attrs"
v-on="listeners"
class="u-drawer bg-white"
:class="extraClasses"
:style="getStyles">
<slot v-if="!hideClose" name="close">
<u-button #click="$emit('input', false)"
class="u-drawer-close-button" icon>
<u-icon icon="x" size="1rem" class="mx-auto" />
</u-button>
</slot>
<slot name="default" />
</div>
</transition>
<transition name="fade">
<div v-if="overlay" #click.stop="closeDrawer" v-show="$attrs.value" style="width:100%; height:100%" class="u-overlay"></div>
</transition>
For whatever reason the transition does not work, it's a drawer that is supposed to slide out but it doesn't, instead it just appears instantaneously.
I should mention that, component3 has been used elsewhere in the project where the sliding effect does occur. The only difference here is that it's within a <slot name=default>. Does anyone know what the problem might be?

Hoist slot content up one level

I hope someone can help me out with this problem! I'm using a Table component from PrimeVue, and I'm looking to create a wrapper component with a slot for content. The problem is, the component will only recognize content directly within its default slot. Nothing else is recognized.
Code:
<template>
<DataTable :value="data">
<!-- These components are recognized -->
<Column v-for="col in cols" :key="col.field" :field="col.field" :header="col.header" />
<slot name="override" :cols="cols">
<!-- These components are not -->
<Column v-for="col in cols" :key="col.field" :field="col.field" :header="col.header" />
</slot>
</DataTable>
</template>
<script>
export default {
data() {
return {
data: // Array of data here,
cols: // Array of cols here
}
}
}
</script>
I checked their implementation, DataTable only looks in $slots.default().children for content. Since the content in <slot name="override"> will show up as something along the lines of $slots.default().children.children, they are not recognized. Is there a way for me to hoist or inject any content (including the default content if possible) from the slot into its parent so the content appears in $slots.default().children?
This template:
<template>
<slot name="heading">
<h1>Default heading</h1>
<h2>Default subheading</h2>
</slot>
</template>
...is effectively the same as conditionally rendering the <slot> with v-if="$slots.heading" (which is only truthy when the heading slot is actually passed in) and moving its inner contents to a v-else block:
<template>
<slot v-if="$slots.heading" name="heading">
</slot>
<template v-else>
<h1>Default Heading</h1>
<h2>Default subheading</h2>
</template>
</template>
So, you can use v-if="$slots.override" to conditionally render the <slot>, and move its contents (i.e., the <Column>s) into a v-else block:
<DataTable :value="data">
<slot v-if="$slots.override" name="override" :cols="cols" />
<Column v-else v-for="col in cols" :key="col.field" :field="col.field" :header="col.header" />
</DataTable>
demo

Vue 3 – <Transition> renders non-element root node that cannot be animated

App.vue has a transition tag to fade the pages out and in.
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in" appear>
<component :is="Component"></component>
</transition>
</router-view>
The Page.vue file has a simple structure, however, it also has a basic sliderjs component which throws the error <Transition> renders non-element root node that cannot be animated. If the transition tag is removed, everything works fine.
<div v-if="page.isReady">
<swiper>
<swiper-slide>Slide 1</swiper-slide>
<swiper-slide>Slide 2</swiper-slide>
<swiper-slide>Slide 3</swiper-slide>
</swiper>
</div>
https://swiperjs.com/vue/
The file also has the following:
import { Swiper, SwiperSlide } from 'swiper/vue';
import 'swiper/swiper.scss';
export default {
components: {
Swiper,
SwiperSlide,
},
setup () {
return {
page: usePage()
}
}
}
Is there any trick to fix the error? Thanks for any tips!
No.
<template>
<div></div>
<div>~someone~</div>
</template>
Yes.
<template>
<div>
  <div></div>
~someone~
</div>
</template>
If you do not use a "div" tag just inside the "Template" tag, you will get the same error. (By the way, it was possible to use other than div tags)
Transitions require single children nodes. Therefore you can wrap the <component> tag inside a <div>, however, a plain <div> inside a <transition> won't trigger the transition, but changing the key attribute does.
We can obtain a unique key by getting the route name:
<router-view v-slot="{ Component, route }">
<transition name="fade" mode="out-in">
<div :key="route.name">
<component :is="Component"></component>
</div>
</transition>
</router-view>
This will effectively transition between routes with a different name, but if you also want to transition between routes of the same name with different parameters, you can use route.fullPath instead of route.name as the key.
I can't fully take credit for this one...but I was having a similar issue and the problem was I had multiple nodes in my view, and found this guy's post on the Vue.js forums:
Found my mistake too. Transition required a single root in components! Since Vue 3 no longer requires a single root node for components I thought this also applies to transitions.
But it’s also logical. CSS requires a single root to which the transitions can refer.
When toggling between elements that have the same tag name, you must tell Vue that they are distinct elements by giving them unique key attributes. Otherwise, Vue’s compiler will only replace the content of the element for efficiency. Even when technically unnecessary though, it’s considered good practice to always key multiple items within a component.
<div>
<router-link to="/"></router-link>
<router-link to="/about"></router-link>
</div>
<router-view v-slot="{ Component, route }">
<transition name="route" :key="route" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
I solved it, it was a minor mistake, there was a character outside a html tag, directly after the tag (comma).
<template>,
<div>
<div>
<swiper>
<swiper-slide>Slide 1</swiper-slide>
<swiper-slide>Slide 2</swiper-slide>
<swiper-slide>Slide 3</swiper-slide>
</swiper>
</div>
</div>
</template>
your dynamic component instance must have a root element.
in you example,'Swiper' and 'SwiperSlide' must have a root element!
don't use RouterView in component parameter of the router. if you need to do that put it inside a root element
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in" appear>
<!-- root element -->
<div>
<component :is="Component"></component>
</div>
</transition>
</router-view>
A solution that worked for me using Nuxt3:
Parent page was:
<template>
<NuxtPage />
</template>
but should be also wrapped into a root node:
<template>
<div>
<NuxtPage />
</div>
</template>
Otherwise I got a transition exception:
Component inside <Transition> renders non-element root node that cannot be animated
I'm doing like this and it works.
<template>
<router-view v-slot="slotProps">
<transition name="route" mode="out-in">
<component :is="slotProps.Component"></component>
</transition>
</router-view>
</template>

Vue.js: Loading template (or div) when user clicks button?

So I currently have a template sitting in a ".vue" file like so:
<template>
<div id="dataAttachToMe"></div>
</template>
I don't want this to load, unless a user clicks a button, something like
<button #click="loadTheTemplateAbove">See Data</button>
I've tried using this example:https://v2.vuejs.org/v2/guide/conditional.html#Controlling-Reusable-Elements-with-key. But it says something like "Component template should contain exactly one root element" in the error message.
I need more than a show/hide here I think, something that can initiate the template dynamically.
<template>
<div id="data">
<button #click="loadTemplate">Load the template</button>
<div v-if="buttonClicked">
<div id="dataAttachedToThisDiv"></div>
</div>
</div>
</template>
The error you are getting, means that there is more than one root element inside <template></template> tag.
It is required in Vue.js (and other template based frameworks/libraries) to have only one root element.
This will NOT work:
<template>
<div id="dataAttachToMe"></div>
<button #click="loadTheTemplateAbove">See Data</button>
</template>
This will work:
<template>
<div id="someRootDiv">
<div id="dataAttachToMe">Some data</div>
<button #click="loadTheTemplateAbove">See Data</button>
</div>
</template>
Here is a code example (App.vue) of what you are trying to achieve:
Basic idea: we have to create a variable, that will be changed upon button click. We add v-if directive that depends on that variable and will handle element's visibility.
Welcome to StackOverflow. When you get the error Component template should contain exactly one root element it means that you can only have one root element in your template. You can fix that error by wrapping everything in a blank div like so
<template>
<div>
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address">
</template>
</div>
</template>
Please edit your post and place you <script> tag. Conditional Rendering requires a data field of a boolean that you can place in your if statement on your template
<template>
<div>
<div v-if="show">{{message}}</div>
<div v-if="#show">Not Showing when show is set to false</div>
<button v-on:click="show = true">Show</button>
</div>
</template>
<script>
module.exports {
data: function () {
message: 'Hello Vue!',
show: false
}
}
</script>