2

Using Vue 3.2.41 - @heroicons/vue 2.0.14 - inertiajs 1.0 - vite 4.0.0

I'm calling a Vue component using this:

<TimelineItem icon="CalendarDaysIcon" />

The component looks like this:

<template>
    <component :is="icon" /> <!-- doesn't work -->
    <CalendarDaysIcon /> <!-- works -->
</template>

<script setup>
    import {
        CalendarDaysIcon,
    } from '@heroicons/vue/20/solid'

    const props = defineProps(['icon'])
</script>

The HTML being rendered is like this:

<calendardaysicon></calendardaysicon> <!-- not what I want -->
<svg> ... </svg> <!-- correct but not dynamic -->

In other words, the <component :is /> is just rendering some empty <calendardaysicon> tags when I'd expect it to render the component. I can see that it has made it lowercase and have no idea how to force it back to PascalCase and I'm not even sure if that would help the situation.

I've simplified the code somewhat, but the full version would have a list of 10 different icons (all part of the Heroicons package which uses PascalCase names) which I'd like to be able to call easily from the main component.

2
  • Possibly, you have a typo in the TimelineItem Commented Feb 7, 2023 at 23:46
  • I believe that the <TimelineItem icon="CalendarDaysIcon" /> is correct because the template is rendering, as is the <CalendarDaysIcon /> within it. Commented Feb 8, 2023 at 6:58

3 Answers 3

2

If you are authoring your templates directly in a DOM (e.g. as the content of a native element), the template will be subject to the browser's native HTML parsing behavior. In such cases, you will need to use kebab-case and explicit closing tags for components https://vuejs.org/guide/essentials/component-basics.html#dom-template-parsing-caveats

Try: <calendar-days-icon> </calendar-days-icon>

Sign up to request clarification or add additional context in comments.

1 Comment

The icon has imported from the } from '@heroicons/vue/20/solid' package as CalendarDaysIcon and that is the only place that it's referred to. Besides, the call to <CalendarDaysIcon /> is working perfectly within the template. It's just the dynamic call which isn't.
0

const { createApp, createElementVNode, openBlock, createElementBlock } = Vue;

const CalendarDaysIcon = {  
  render(_ctx, _cache) {
  return (openBlock(), createElementBlock("svg", {
    xmlns: "http://www.w3.org/2000/svg",
    viewBox: "0 0 20 20",
    fill: "currentColor",
    "aria-hidden": "true"
  }, [
    createElementVNode("path", { d: "M5.25 12a.75.75 0 01.75-.75h.01a.75.75 0 01.75.75v.01a.75.75 0 01-.75.75H6a.75.75 0 01-.75-.75V12zM6 13.25a.75.75 0 00-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 00.75-.75V14a.75.75 0 00-.75-.75H6zM7.25 12a.75.75 0 01.75-.75h.01a.75.75 0 01.75.75v.01a.75.75 0 01-.75.75H8a.75.75 0 01-.75-.75V12zM8 13.25a.75.75 0 00-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 00.75-.75V14a.75.75 0 00-.75-.75H8zM9.25 10a.75.75 0 01.75-.75h.01a.75.75 0 01.75.75v.01a.75.75 0 01-.75.75H10a.75.75 0 01-.75-.75V10zM10 11.25a.75.75 0 00-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 00.75-.75V12a.75.75 0 00-.75-.75H10zM9.25 14a.75.75 0 01.75-.75h.01a.75.75 0 01.75.75v.01a.75.75 0 01-.75.75H10a.75.75 0 01-.75-.75V14zM12 9.25a.75.75 0 00-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 00.75-.75V10a.75.75 0 00-.75-.75H12zM11.25 12a.75.75 0 01.75-.75h.01a.75.75 0 01.75.75v.01a.75.75 0 01-.75.75H12a.75.75 0 01-.75-.75V12zM12 13.25a.75.75 0 00-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 00.75-.75V14a.75.75 0 00-.75-.75H12zM13.25 10a.75.75 0 01.75-.75h.01a.75.75 0 01.75.75v.01a.75.75 0 01-.75.75H14a.75.75 0 01-.75-.75V10zM14 11.25a.75.75 0 00-.75.75v.01c0 .414.336.75.75.75h.01a.75.75 0 00.75-.75V12a.75.75 0 00-.75-.75H14z" }),
    createElementVNode("path", {
      "fill-rule": "evenodd",
      d: "M5.75 2a.75.75 0 01.75.75V4h7V2.75a.75.75 0 011.5 0V4h.25A2.75 2.75 0 0118 6.75v8.5A2.75 2.75 0 0115.25 18H4.75A2.75 2.75 0 012 15.25v-8.5A2.75 2.75 0 014.75 4H5V2.75A.75.75 0 015.75 2zm-1 5.5c-.69 0-1.25.56-1.25 1.25v6.5c0 .69.56 1.25 1.25 1.25h10.5c.69 0 1.25-.56 1.25-1.25v-6.5c0-.69-.56-1.25-1.25-1.25H4.75z",
      "clip-rule": "evenodd"
    })
  ]))
}
}

const TimeLineItem = {   
  props: ['icon'],
  components: {
    CalendarDaysIcon
  },
  template: '<component :is="icon" class="icon"/>'
}

const App = {
  components: { 
    TimeLineItem
  } 
}

const app = createApp(App)
app.mount('#app')
.icon {
    width: 36px;
    height: 36px;
}
<div id="app">  
  <time-line-item icon="CalendarDaysIcon"></time-line-item>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>

1 Comment

Thank you, I understand and accept that this code works correctly within the presented context. However, I believe that I I am doing exactly the same thing as this in the question I wrote, just in a different format because the CalendarDaysIcon component is available and working and the prop is coming through correctly. The issue seems to be that <component :is> is not working as expected in this format.
0

Using <component :is="icon" /> is only using a string containing CalendarDaysIcon

Instead, in the main component, pass the actual component reference like this:

<template>
  <TimelineItem :icon="CalendarDaysIcon" />
</template>

<script setup>
    import {
        CalendarDaysIcon,
    } from '@heroicons/vue/20/solid'

    const props = defineProps(['icon'])
</script>

Then, in the TimelineItem component, there is no need to reference any icons:

<template>
    <component :is="icon" /> <!-- now works -->
</template>

<script setup>
    const props = defineProps(['icon'])
</script>

Thanks to @Robert Boes on the Inertia Discord server for the guidance.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.