antoniandre / wave-ui

A UI framework for Vue.js 3 (and 2) with only the bright side. ☀️
https://antoniandre.github.io/wave-ui
MIT License
549 stars 40 forks source link

Components in Tabs are rendered multiple times #83

Closed nightyx closed 2 years ago

nightyx commented 2 years ago

Hi there. I´m using Wave-UI tabs and within each of those tabs I load one of my custom components. When my custom components are created/mounted, I´m calling a method to fetch data from my API. However, my issue is, that whenever I switch from one tab to the next tab, the created function is called as often as I have tabs. (to be precise: after loading the page, the first time switching from tab A (default) to B, tab B is 'created' twice, if I then navigate from B to C, the content is 'created' three times.)

At first I thought, the page is rendered multiple times as the website tries to fetch the data. Thus I added a v-if and I set the boolean to true after fetching my data. However. This didnt work out.

Then i added a fourth tab to my code, and after a bit of switching tabs, all of the content is being created four times.

Is this a bug in WaveUI or a bug in my code?

<template>
<w-app>
    <w-tabs :items="tabs">
        <template #item-content="{ item }">
            <component :is="item.component"></component>
        </template>
    </w-tabs>
  </w-app>
</template>
<script>
import A from "@/components/A.vue";
import B from "@/components/B.vue";
import C from "@/components/C.vue";
export default {
  name: "App",
  components: {
    A,
    B,
    C,
  },
  data: () => ({
    tabs: [
      { title: "title1", component: A},
      { title: "title2", component: B},
      { title: "title3", component: C},
    ],
  }),
};
</script>

And one of the components.. i removed some code to make it more readable

<template>
  <div v-if="isDataLoaded">
      <div v-for="group in groups" :key="group">
        <w-card
          no-border
          @click="doSth(group.id, group.state)">
              <div>{{group.name}}</div>
        </w-card>
      </div>
  </div>
</template>

<script>
export default {
  name: 'A',

  data() {
    return {
      groups: [],
      isDataLoaded: Boolean,
    };
  },

  created() {
    this.getGroups();
  },

  methods: {
    async getGroups() {
      try {
        const response = await this.$axios.get('/api/get/all/group/');
        this.groups = response.data;
        this.isDataLoaded = true;
      } catch (err) {
        this.isDataLoaded = false;
      }
    },
...
// that doSth method does not fetch data.
antoniandre commented 2 years ago

Hi @nightyx, That's funny it behaves like this on Vue 3 whereas it is all working normally on Vue 2. See this Vue 2 example of the same code: https://codepen.io/antoniandre/pen/yLpOePj?editors=0010!

But anyway, I have now fixed it for Vue 3 in version 2.32.3 :) Here is a Vue 3 demo: https://codepen.io/antoniandre/pen/GRyZeNY?editors=0010

Also note that if you define the component in each item in the data like: { title: 'Tab 1', component: Component1 }, Vue 3 will raise a warning that you don't need to make the component reactive. So you should just set the item component as a string and define the components in components like so.

export default {
  components: { Component1, Component2, Component3 },

  data: () => ({
    tabs: [
      { title: 'Tab 1', component: 'component1' },
      { title: 'Tab 2', component: 'component2' },
      { title: 'Tab 3', component: 'component3' }
    ]
  })
}

Hope it helps.

nightyx commented 2 years ago

Thanks for the quick patch :) that worked for me. Also thanks for the hint on reactivity. Now I just need to figure out how I can force the components in my tabs to re-render on command to fetch data from an api XD