Search code examples
vue.jsvuejs3vue-composition-apipinia

Rendering dynamic components from Pinia doesn't work with :is


I have this code. It works fine except when it goes to render my simple components. In fact when I click on tabs, all Pinia works, but it breaks on rendering component.

this is the Pinia store:

import { defineStore } from 'pinia'

export const useTabs = defineStore('tabs',  {
  state: () => {return{
    ready: false,
    selected: {},
    list:[
        {
            name: "Pubblicazione",
            icon: "fas fa-image",
            component: () => import('@Squadrario/Views/Pubblicazione.vue')
        },
        {
            name: "Edit",
            icon:"fas fa-music",
            component: () => import('@Squadrario/Views/Edit.vue')
        },
    ],
  }},
  getters: {
    isActive: (state) => {
        return (id) => state.selected.name == state.list[id].name
    },
    activeComponent: (state) => {
        return state.selected.component;
    }
  },
  actions: {
    init(){
      this.selected = this.list[0];
      this.ready = true;
    },
    activate(id){
        this.selected = this.list[id];
    },
  },
})

Instead this is the main component

<script setup>
/* imports */
import { ref, onBeforeMount,markRaw } from 'vue'
import { storeToRefs} from 'pinia'

/* stores */
import { useTabs } from './stores/tabs.js';
const tabs = useTabs();
const { isActive } = storeToRefs(tabs);
const { activeComponent } = storeToRefs(tabs);

onBeforeMount(() => {
    tabs.init();
  })
</script>
<template>
    <div v-if="tabs.ready" class="tabs is-toggle is-centered">
        <ul>
            <li v-for="(item,id) in tabs.list" :class="{'is-active': isActive(id)}" @click="tabs.activate(id)">
                <a>
                    <span class="icon is-small"><i :class="item.icon" aria-hidden="true"></i></span>
                    <span>{{ item.name }}</span>
                </a>
            </li>
        </ul>
    </div>
    <Transition>
        <keep-alive>
            <component :is="activeComponent"></component>
        </keep-alive>
    </Transition>
</template>
<style>
</style>

These are the two components that I'd like to render with tabs:

/* a simple component Pubblicazione.vue */

<template>
    PUBBLICAZIONE
</template>
/* another simple component Edit.vue */

<template>
    EDIT
</template>

Why when I change page in tabs I obtain an error TypeError: d is null ...? I think that my mistake is in <component :is="activeComponent"></component> May you help me please?


Solution

  • Your list component property is technically just an import function and not the actual component. It should be wrapped in defineAsyncComponent to actually load and retrieve the component when needed.

    list: [
      {
        name: 'Pubblicazione',
        icon: 'fas fa-image',
        component: defineAsyncComponent(() => import('@Squadrario/Views/Pubblicazione.vue')
      },
      {
        name: 'Edit',
        icon: 'fas fa-music',
        component: defineAsyncComponent(() => import('@Squadrario/Views/Edit.vue')
      }
    ]
    

    Also for performance the getter should return the raw component object

    activeComponent: state => {
      return toRaw(state.selected.component)
    }