Fix: Slot Invoked Outside of Render Function in Vue.js

Error message:
Slot "{key}" invoked outside of the render function.
Components 2025-01-25

What Causes This Warning?

This warning occurs when you invoke a slot function outside of the render function or template rendering context. Slots should only be called during rendering.

The Problem

export default {
  setup(props, { slots }) {
    // ❌ Calling slot in setup
    const content = slots.default?.()

    // ❌ Calling slot in lifecycle hook
    onMounted(() => {
      const header = slots.header?.()
      console.log(header)
    })
  }
}

The Fix

Call Slots in Render Function

export default {
  setup(props, { slots }) {
    // ✅ Return render function that calls slots
    return () => {
      return h('div', [
        slots.header?.(),
        slots.default?.(),
        slots.footer?.()
      ])
    }
  }
}

With Template (Automatic)

<template>
  <!-- ✅ Slots are invoked automatically in templates -->
  <div>
    <header>
      <slot name="header" />
    </header>
    <main>
      <slot />
    </main>
  </div>
</template>

Common Scenarios

Checking Slot Content

// ❌ Wrong - invoking slot to check content
export default {
  setup(props, { slots }) {
    const hasHeader = slots.header?.().length > 0

    return () => h('div', hasHeader ? slots.header() : null)
  }
}

// ✅ Correct - check slot existence, invoke in render
export default {
  setup(props, { slots }) {
    return () => {
      const hasHeader = !!slots.header

      return h('div', [
        hasHeader ? h('header', slots.header()) : null,
        slots.default?.()
      ])
    }
  }
}

Conditional Slot Rendering

export default {
  setup(props, { slots }) {
    const showHeader = ref(true)

    // ✅ All slot calls inside render
    return () => h('div', [
      showHeader.value && slots.header
        ? h('header', slots.header())
        : null,
      slots.default?.()
    ])
  }
}

Slot with Fallback

export default {
  setup(props, { slots }) {
    return () => h('div', [
      // ✅ Fallback in render function
      slots.header?.() ?? h('h1', 'Default Header'),
      slots.default?.() ?? h('p', 'No content provided')
    ])
  }
}

Scoped Slots

export default {
  props: ['items'],
  setup(props, { slots }) {
    return () => h('ul', props.items.map((item, index) =>
      h('li', [
        // ✅ Pass slot props during render
        slots.default?.({ item, index }) ?? item.name
      ])
    ))
  }
}

Logging Slot Content

// ❌ Can't log slot VNodes
export default {
  setup(props, { slots }) {
    console.log(slots.default?.()) // Warning!
  }
}

// ✅ Log in render or use slot check
export default {
  setup(props, { slots }) {
    console.log('Has default slot:', !!slots.default)

    return () => {
      const content = slots.default?.()
      console.log('Rendered slot content') // OK during render
      return h('div', content)
    }
  }
}

Wrapper Component

export default {
  setup(props, { slots, attrs }) {
    return () => h(
      'div',
      { class: 'wrapper', ...attrs },
      // ✅ Forward slots in render
      slots.default?.()
    )
  }
}

Computed Based on Slots

export default {
  setup(props, { slots }) {
    // ✅ Check slot existence (not invoke)
    const hasCustomFooter = computed(() => !!slots.footer)

    return () => h('div', [
      slots.default?.(),
      hasCustomFooter.value
        ? h('footer', slots.footer())
        : h('footer', 'Default Footer')
    ])
  }
}

Quick Checklist

  • Only call slots inside render function
  • Use !!slots.name to check existence (not invoke)
  • Templates handle slot invocation automatically
  • Scoped slots: pass props when calling slots.name({ props })
  • Use slots.default?.() for safe invocation
  • Fallbacks: slots.name?.() ?? fallbackContent