Navigable Vuetify Tabs with routing capabilities

Within my Vue application, I have a page that contains various tabs. My goal is to display different tabs based on the routes being accessed.

To achieve this functionality, I followed an answer provided in this link.

Overall, it's working well! I can even switch tabs by swiping on mobile devices, thanks to the @change listener attached to the v-tabs-items.

However, when I click on the tab labels, the component loaded via the <router-view> seems to be mounted twice. On the contrary, when swiping, it's only mounted once.

This issue seems to stem from the fact that the <router-view> is nested within the loop of <v-tab-item>s.

If I move it outside of this loop, the child components are properly mounted just once. Unfortunately, this means sacrificing the swipe feature because the content becomes detached.

So: Is there a way to preserve both functionalities (dynamically routed content and swipability)?

Thank you!


Vue:

<template>
 <!-- [...] -->
 <v-tabs centered="centered" grow v-model="activeTab">
    <v-tab v-for="tab of tabs" :key="tab.id" :id="tab.id" :to="tab.route" exact>
      <v-icon>{{ tab.icon }}</v-icon>
    </v-tab>

    <v-tabs-items v-model="activeTab" @change="updateRouter($event)">
      <v-tab-item v-for="tab of tabs" :key="tab.id" :value="resolvePath(tab.route)" class="tab_content">
        <!-- prevent loading multiple route-view instances -->
        <router-view v-if="tab.route === activeTab" />
      </v-tab-item>
    </v-tabs-items>
  </v-tabs>
  <!-- [...] -->
</template>

<script lang="ts">
data: () => ({
    activeTab: '',
    tabs: [
      {id: 'profile', icon: 'mdi-account', route: '/social/profile'},
      {id: 'friends', icon: 'mdi-account-group', route: '/social/friends'},
      {id: 'settings', icon: 'mdi-cogs', route: '/social/settings'},
    ]
  }),
  methods: {
    updateRouter(tab:string) {
      this.$router.push(tab)
    }
  },
</script>

Router:

{
    path: "/social",
    component: () => import("../views/Social.vue"),
    meta: {
      requiresAuth: true
    },
    children: [
      {
        path: "profile",
        component: () => import("@/components/social/Profile.vue")
      },
      {
        path: "friends",
        component: () => import("@/components/social/Friendlist.vue")
      },
      {
        path: "settings",
        component: () => import("@/components/social/ProfileSettings.vue")
      }
    ]
  }

Answer №1

Is it possible for this behavior to represent the sequence of events accurately? When @change updates the route post activeTab, clicking the tab updates the route and then activeTab? As a result, the router-view is on the next view before the tab-view gets updated, displaying two different router-views on the same path.

To resolve this issue:

<router-view v-if="tab.route === activeTab" />

Change it to:

<router-view v-if="tab.route === $route.fullPath && tab.route === activeTab" />

or:

<router-view v-if="tab.route === $route.path && tab.route === activeTab" />

Answer №2

My perspective differs from @Estradiaz's answer. The solution provided didn't suit my needs as I have child views within some of my tabs, causing redirects from the tab to the first child view on that tab. This led to instances where $route.path did not match tab.route due to $route.path pointing to one of the children within that tab.

After exploring other options, I discovered an alternative approach which I believe is more effective and widely applicable. Before delving into that, let me thoroughly explain the situation at hand.

By utilizing the :to attribute of the , we are allowing the router to handle all component selection and display tasks. Consequently, the route changes before the infrastructure can react. For example, if we have 2 tabs and are currently viewing tab 1, the displayed content in the HTML would be:

<v-tabs-items>
  <v-tab-item><router-view>tab 1 component</router-view></v-tab-item>
  <--- tab 2 commented out --->
</v-tabs-items>

Upon clicking on tab 2, the scenario shifts to:

<v-tabs-items>
  <v-tab-item><router-view>tab 2 component</router-view></v-tab-item>
  <--- tab 2 commented out --->
</v-tabs-items>

Subsequently, after a period of time, the code updates, transitioning to:

<v-tabs-items>
  <--- tab 1 commented out --->
  <v-tab-item><router-view>tab 2 component</router-view></v-tab-item>
</v-tabs-items>

Due to the change in v-if condition, the component for tab 1 gets destroyed along with the new tab 2 component, only for the component for tab 2 to create a fresh instance of itself.

It appears there is no mechanism within the structure to detect the mismatch between the active tab and the component in the . While @Estradiaz's solution may suffice for basic tab components, it lacks reliability.

Rather than attempting to avoid the mismatch case, my solution embraces it by leveraging the keep-alive infrastructure to "reuse" the when removing tab 1's . Here is how the revised markup looks based on your original template:

<template>
 <!-- [...] -->
 <v-tabs centered="centered" grow v-model="activeTab">
    <v-tab v-for="tab of tabs" :key="tab.id" :id="tab.id" :to="tab.route" exact>
      <v-icon>{{ tab.icon }}</v-icon>
    </v-tab>

    <v-tabs-items v-model="activeTab" @change="updateRouter($event)">
      <v-tab-item v-for="tab of tabs" :key="tab.id" :value="resolvePath(tab.route)" class="tab_content">
        <div v-if="tab.route === activeTab">
          <keep-alive>
            <router-view key="1"  />
          </keep-alive>
        </div>
      </v-tab-item>
    </v-tabs-items>
  </v-tabs>
  <!-- [...] -->
</template>

Upon clicking on tab 2, the following sequence unfolds:

<v-tabs-items>
  <v-tab-item><router-view>tab 2 component</router-view></v-tab-item>
  <--- tab 2 commented out --->
</v-tabs-items>

As the router-view now generates the tab 2 component and showcases it. With the passage of time, the v-tab infrastructure updates, leading to the markup transformation:

<v-tabs-items>
  <--- tab 1 commented out --->
  <v-tab-item><router-view>tab 2 component</router-view></v-tab-item>
</v-tabs-items>

However, when the initial is removed from the DOM, it isn't deleted but cached instead. Subsequently, when the new is generated, it comes from the cache, ready to go with the pre-constructed tab 2 component.

In my scenario, this method effectively resolved the issue of double mounting and potentially offers improved performance by avoiding repetitive recreations of the during tab switches.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

HighStock chart malfunctioning with inaccurate epoch datetime display

I am working on a project that involves creating a dynamic Highstock chart to showcase the daily influx of emails. The data is stored in a JSON file that gets updated every day, and you can see a snippet of it below: [{ "name": "Month", "data": [147199320 ...

Collecting information from form submissions, accessing data from the database, and displaying the results

I am currently working on a project using nodejs, express, and mongodb within the cloud9 IDE. My goal is to create a page with a form that allows users to input data, search for corresponding information in the database, and display that data on the same ...

Sorting table by priority in HTML is not functioning as expected

I am currently developing this code for a SharePoint 2010 platform. While the table can currently be sorted alphabetically, I am looking to implement a different functionality. Unfortunately, I am facing an issue with changing variables 'a' and ...

Swapping out bullet points for delicious food icons in an unordered list on an HTML page

I am working with the following code snippet: <div id="instructions"> <h3>Get it done</h3> <ol> <li>In a blender add the eggs, chocolate powder, butter, flour, sugar and milk.</li> <li>Then whisk ...

Swap out the image backdrop by utilizing the forward and backward buttons

I am currently working on developing a Character Selection feature for Airconsole. I had the idea of implementing this using a Jquery method similar to a Gallery. In order to achieve this, I require a previous button, a next button, and the character disp ...

Ways to verify that a javascript function generates an object and executes a method within that object

Currently, I am in the process of updating server code written in nodejs and incorporating unit tests into the mix. However, I have encountered a challenge that I need assistance with: classX.prototype.methodX = function () { // Create new session ...

I am receiving an undefined response from Cheerio when attempting to fetch JSON data

My goal is to create a web scraper and I have successfully downloaded the HTML. Now, with this code snippet, I am attempting to extract the title from my HTML: fs.readFile(__filename.json , function (err, data) { if(err) throw err; const $ = cheerio.load ...

Steps for transferring JSON data from the controller to JavaScript

Within my cluster Table, there is a column called description which stores cluster coordinates in JSON format. I want to draw multiple cluster polygons on Google Maps using these coordinates. id | description ...

Using AngularJS location.path for unique custom URLs

Control Code: $scope.$on('$locationChangeStart', function () { var path = $location.path(); var adminPath = '/admin/' ; if(path.match(adminPath)) { $scope.adminContainer= function() { return true; }; }); HTML <div clas ...

Retrieve the information from the API and populate the tables with the data

I'm currently working on fetching API data and displaying it in tables, using mock data for now. Successfully implemented actions and reducers. Managed to call the API but encountered an issue with network calls where I see a blocked response content ...

Unable to access the property 'function' of an undefined value

I've been attempting to call a function from another function within my React application. However, I am encountering an error that reads: Error in login TypeError: Cannot read property 'loadDashboard' of undefined. Despite researching simil ...

Instructions for utilizing float:left and list-style-type within HTML are as follows: I am currently experiencing issues with implementing the float:left and list-style-type properties in my code

I'm attempting to align a button list to the left using the float: left property and also remove list styles, but for some reason it's not working as expected. //CSS #tus{margin:5px;padding:0;width:640px;height:auto;} #tus ul{margin:0px;padding: ...

The message vanishes upon refreshing the page

I've developed a socket.io web app. When I click on the button to send a message, the message appears briefly but disappears when the page refreshes unexpectedly. How can I prevent this random refreshing and ensure that socket.io saves my messages? B ...

Angular - Evaluating the differences between the object model and the original model value within the view

To enable a button only when the values of the 'invoice' model differ from those of the initial model, 'initModel', I am trying to detect changes in the properties of the 'invoice' model. This comparison needs to happen in th ...

How can a JavaScript function be used to check a tag's status?

I currently have two select tags on my webpage. I want to ensure that only one option can be selected at a time from these two tags. If the user tries to select options from both tags, an error message should be displayed instructing them to choose only on ...

Error in JSON due to the presence of an unexpected token within the

I am facing a challenge with my PHP code, where I take a list of filenames or empty strings and store them in an array. This array is then converted to JSON and saved in a database successfully. However, the problem arises when this array is also stored wi ...

I am looking to display the pop-up exclusively when the page is clicked, but unfortunately it is also appearing when the Menu is clicked

I've been working on this code and trying to make some modifications, but I just can't seem to find the right solution for my issue <!-- Updated main content --> <main> <p> Click on the menu button located in the top right ...

Tips for extracting the index of the chosen item from a dropdown using ReactJs and Material UI

This is the code snippet for Dropdown component implementation: <DropDownField name={formElement.name} label={formElement.name} value={formik.values[formElement.name] || ''} dropDownItems={formElement.name === &apo ...

Instructions on removing rows by using buttons within a JavaScript-generated table

This snippet displays JS code to create a quiz index table and HTML code to display the index. function load(){ var data = [ { "id": "qc1111", "quizName": "Quiz1", "course": "111", "dueDate": "1/ ...

Achieve top-notch performance with an integrated iFrame feature in Angular

I am trying to find a method to determine if my iframe is causing a bottleneck and switch to a different source if necessary. Is it possible to achieve this using the Performance API? This is what I currently have in my (Angular) Frontend: <app-player ...