CodeSpent
CodeSpent

Reputation: 1904

Component not updating on key change

It is my understanding that adding a key attribute to a component will allow the component to be reactive when that key changes, but with a v-navigation-drawer provided by Vuetify this seems to have no impact.

I've tried even arbitrary changes to a loggedIn key such as += 1 which would be a change sufficient enough to re-render the component.

The use case here is that I have a global navigation drawer that should show context based on if the user is/isn't logged in.

Full component

<template>
  <v-navigation-drawer
    absolute
    right
    dark
    color="primary"
    v-if="$store.state.globalDrawer"
    v-model="$store.state.globalDrawer"
    :key="loggedIn"
  >

  <v-container class="pa-5">
    <v-layout column align-center justify-center>

      <v-avatar class="ma-4" size="60">
        <img v-if="!twitter" src="https://pbs.twimg.com/profile_images/1173674959731867648/6kzApb83_400x400.jpg" />
        <img v-else :src="twitter.profile_image_url" />
      </v-avatar>

      <h2 v-if="!twitter" class="title">StreamBeacon.tv</h2>
      <h2 v-else class="title">{{ twitter.display_name }}</h2>

      <p v-if="!twitter" class="body-2 text-center">Enhance your Going Live experience.</p>
      <p v-else class="body-2 text-center">@{{ twitter.screen_name }}</p>

      <v-chip v-if="twitch.live">
        <font-awesome-icon :icon="['fab', 'twitch']" />  
        &nbsp;Live Now
      </v-chip>
    </v-layout>
  </v-container>

    <!-- User is authenticated -->
    <v-list dense nav>
      <v-subheader>Account</v-subheader>
      <v-list-item-group 
        color="primary"
        v-if="user"
      >
        <v-list-item
          v-for="(link, i) in accountLinks"
          :key="i"
          :color="activeLinkColor"
          :to="link.target"
          @click="changeDashboardView(link.dashboardComponent)"
        >
          <v-list-item-icon :color="activeLinkColor">
            <v-icon v-text="link.icon"></v-icon>
          </v-list-item-icon>
          <v-list-item-content>
            <v-list-item-title v-text="link.text"></v-list-item-title>
          </v-list-item-content>
        </v-list-item>
      </v-list-item-group>

    <!-- User is NOT authenticated -->
    <v-list-item-group 
      color="primary"
      v-else
    >
        <v-list-item
          @click="$eventHub.$emit('registration')"
          :color="activeLinkColor"
        >
          <v-list-item-icon color="primary">
            <v-icon>mdi-shield-account</v-icon>
          </v-list-item-icon>
          <v-list-item-content>
            <v-list-item-title>Sign In</v-list-item-title>
          </v-list-item-content>
        </v-list-item>
      </v-list-item-group>

      <v-subheader>Quick Links</v-subheader>
      <v-list-item-group 
        color="primary"
      >
        <v-list-item
          v-for="(link, i) in quickLinks"
          :key="i"
          :color="activeLinkColor"
        >
          <v-list-item-icon color="primary">
            <v-icon v-text="link.icon"></v-icon>
          </v-list-item-icon>
          <v-list-item-content>
            <v-list-item-title v-text="link.text"></v-list-item-title>
          </v-list-item-content>
        </v-list-item>
      </v-list-item-group>

    </v-list>
  </v-navigation-drawer>
</template>

<script>
export default {
  data() {
    return {
      user: false,
      twitter: false,
      twitch: false,
      loggedIn: false,
      active: this.$store.state.globalDrawer,
      activeLinkColor: "#fff",
      accountLinks: [
        {
          "icon": "mdi-shield-account",
          "dashboardComponent": null,
          "text": "My Account",
        },
        {
          "icon": "mdi-account-box-multiple",
          "dashboardComponent": "connections",
          "text": "My Connections"
        },
        {
          "icon": "mdi-credit-card",
          "dashboardComponent": "subscription",
          "text": "Subscription",
        },
        {
          "icon": "mdi-bug",
          "dashboardComponent": "support",
          "text": "Support"
        },
        {
          "icon": "mdi-logout",
          "target": "logout",
          "text": "Logout"
        }
      ],
      quickLinks: [
        {
          "icon": "mdi-gift",
          "click": "",
          "text": "Gift a Streamer"
        },
        {
          "icon": "mdi-mail",
          "click": "",
          "text": "Contact Us"
        },
        {
          "icon": "mdi-library-video",
          "click": "",
          "text": "Our Streamers"
        },
      ]
    }
  },
  methods: {
    checkIfAuthenticated() {
      if(this.$store.state.authenticatedUser != null) {
        this.user = JSON.parse(this.$store.state.authenticatedUser)
        this.loggedIn = true
        if(this.user.twitter) {
          this.twitter = this.user.twitter
        }
        if(this.user.twitch_channel) {
          this.twitch = this.user.twitch_channel
        }
      }
    },
    changeDashboardView(targetComponent) {
      if(this.$route.name != 'dashboard') {
        this.$router.replace('dashboard')
        .then(() => {
          this.$eventHub.$emit('changeDashboardComponent', targetComponent)
        })
      } else {
        this.$eventHub.$emit('changeDashboardComponent', targetComponent)
      }
    }
  },
  mounted() {
    this.checkIfAuthenticated()
  },
  watch: {
    $route: function(to, from) {
      this.loggedIn true
    }
  }
}
</script>

Are there circumstances that need to be in order for key changes to be effective, or should this be working? I have verified that loggedIn does properly change when logging in.

Another theory I had was that because of the v-if directive, the component was gone during the key change, however, I debunked this theory already.

Upvotes: 1

Views: 1386

Answers (1)

CodeSpent
CodeSpent

Reputation: 1904

The underlying issue, as Antonio above mentioned, is that I am not using any reactive data for the key & dependencies of the v-if directive.

If we look using my code above, after logging in, user is still false, which is a value dependency of the context for an authenticated user. On the mounted() hook & a route change I look for these & update them, but that's a hacky approach.

After logging in:

enter image description here

The solution was to use a computed property using a Vuex getter which will trigger a render on the component.

enter image description here

This being said, the changes I made to the code above are as follows (subject to change since this goes against current mistakes I need to fix, but keeping it as close to the original to make it easy to read differences).

  computed: {
    loggedIn: {
      get() {
        return this.$store.state.isLoggedIn
      },
      set(value) {
        this.$store.commit('updateLoggedInState', value)
      }
    },
    user: {
      get() {
        return JSON.parse(this.$store.state.authenticatedUser)
      }
    }
  }

Now after logging in enter image description here

Easy solution, and a good lesson for the day! Thanks, Antonio for the guidance.

Upvotes: 1

Related Questions