question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Child component <slot v-if="false"> will still evaluate parent slot code

See original GitHub issue

Version

2.5.16

Reproduction link

https://github.com/bbugh/v-if-slot-issue/commit/ffd6d476bf378936566b4916c1342af4310ef412

Steps to reproduce

  1. Create a child component with a conditional slot:

    <template>
      <div>
        <slot v-if="showSlot" />
      </div>
    </template>
    
  2. Use the slot in a parent component, using code that should not be called when v-if resolves as false:

    <conditional-slot :showSlot="false">
      {{ example.should.not.be.called }}
    </conditional-slot>
    
    data: () => ({
      example: {}
    })
    

Observe that slot code is called, and fails because “example” is an empty object.

A runnable demo of this is in the linked repository.

What is expected?

The behavior is expected to match v-if’s behavior in other circumstances, which is that the code in the if block is not evaluated when the conditional check resolves as false.

What is actually happening?

The parent slot code is always evaluated, regardless of the value of v-if in the child component.

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:9
  • Comments:10 (3 by maintainers)

github_iconTop GitHub Comments

18reactions
sirlancelotcommented, May 1, 2019

You can use the new v-slot syntax (<template #default></template>) to prevent the slot content from rendering until its needed.

v-slot documentation

9reactions
eZanmotocommented, May 11, 2020

I’d just like to elaborate on @yyx990803 and @sirlancelot’s answers with a bit more detail for people landing on this page looking for quick answers. Take the following code as an example (preview at https://jsfiddle.net/yd2Lpcf1/4/):

<div id="app">
  <div>
    <div v-if="!person">Loading</div>
    <div v-else>Hi {{person.name}}</div>
    
    <layout :loading="!person">
      Hi {{person.name}}
    </layout>
  </div>
</div>

<template id="layout">
  <div>
    <div v-if="loading">Loading</div>
    <slot v-else></slot>
  </div>
</template>
Vue.component('layout', {
  template: '#layout',
  props: ['loading']
});

new Vue({
	el: '#app',
  data() {
    return { person: null }
  },
  mounted: function () {
    setInterval(() => {
	    this.person = this.person ? null : {name: 'John'}
    }, 1000)
  }
});

When this is run in the fiddle linked above then the preview will be blank for a second, then “Hello John” will be shown twice. If run in its own page, then the error console will show the error Cannot read property 'name' of null, referring to the fact that person.name defined in the <layout> section was eagerly evaluated and raised an error. This error is thrown every second. This demonstrates that slots are evaluated eagerly and that the error halts the rendering of the child component and the parent component - “Loading” isn’t printed in the first v-if, nor in the one in the child component. It also demonstrates that, even though the slot was eagerly evaluated originally, it is still re-rendered when the value of person changes.

Following @sirlancelot’s recommendation, the only thing that needs to change is to nest an extra <template> layer inside the <layout>:

    <layout :loading="!person">
      <template #default>
        Hi {{person.name}}
      </template>
    </layout>

When the page is re-run then the preview will show “Loading” twice, for a second, and then “Hello John” will be shown twice, and these values will change every second. The error console will be blank. This demonstrates that the slot is lazily evaluated following the expected v-if logic, as expected, and the whole component is properly re-evaluated reactively, instead of just being re-rendered.

In short, when you’re using a component with a conditional slot like <MyBaseComponent>...</MyBaseComponent>, simply wrap the content as @sirlancelot described (<MyBaseComponent><template #default>...</template></MyBaseComponent>), and the default slot will evaluate in the way you’d expect.

As an aside, if support for Vue 2.0 is expected to continue for much longer, I would recommend updating the documentation for slots with a section on the “Eager Evaluation” caveat and how it can be solved using the v-slot workaround.

Read more comments on GitHub >

github_iconTop Results From Across the Web

VueJS slots: Why child method passed in via slot from parent ...
In this demo, I pass in a method to a child component (defined in child) via slots from parent. However that method does...
Read more >
Vue Tricks - Passing Slots to Child Components - Dev Induct
In the end, the focus is going to be on passing all slots from the parent component to the child. This article assumes...
Read more >
Parent & Child Components - Halogen Guide
This slot is a place where the component can produce Halogen HTML until it ... the child component, which lets the parent component...
Read more >
Can't access data property from parent in slot child component
I have two components rad-list and rad-card, I placed a slot element in rad-list where I will place rad-card, now rad-card receices an...
Read more >
Slots - Vue.js
parent component passing slot content FancyButton('Click me! ... Slot content does not have access to the child component's data.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found