Search code examples
javascriptvue.jslocal-storagevuexvue-cli-3

Vue.js: Show/Hide buttons on navbar based on Vuex Store state when the user is logged in or not


I'm creating a navbar that shows or hides buttons depending if the user is logged in or not. For that, I'm saving the state on Vuex and localStorage.

I'm trying to build a dynamic menu, using a list of objects (i.e. rightMenu) that contains the information of the buttons (i.e. route, title and a flag that indicates if the button may show or not if the user is logged in).

Always that the user logs in the system, the this.$store.state.auth.isUserLoggedIn changes to true, however the template does not change, the button stays in the same initial state when the user was not logged in. For example: the sign out button does not show when this.$store.state.auth.isUserLoggedIn updates. But when I click 'ctrl+F5' and the page reloads, the buttons show correctly. In this case, for example, the sign out button appears correctly when I reload the page manually.

I'm thinking in to force the page to reload again when the user logs in or logs out, however I believe that it is not a good option.

Could anyone help me?

I'm giving the code that I'm using below.

Thank you in advance.

Menu.vue > template

<div>
    <v-toolbar color='grey darken-3' dark>
        <v-toolbar-title>Site</v-toolbar-title>

        ...

        <v-toolbar-items class='hidden-sm-and-down'>
            <v-btn v-for='item in rightMenu' :key='item.title'
                   :to='item.to' v-if='item.showButton' flat>
                   {{ item.title }}
            </v-btn>
        </v-toolbar-items>

    </v-toolbar>

    <router-view/>
</div>

Menu.vue > script

export default {
  data () {
    return {
      rightMenu: [
        { to: '/sign_in', title: 'sign in'
          showButton: !this.$store.state.auth.isUserLoggedIn },
        { to: '/sign_up', title: 'sign up'
          showButton: !this.$store.state.auth.isUserLoggedIn },
        { to: '/sign_out', title: 'sign out'
          showButton: this.$store.state.auth.isUserLoggedIn }
      ]
    }
  },
  ...
}

store.js

const store = new Vuex.Store({
  state: {
    auth: {
      token: '',
      isUserLoggedIn: false
    }
  },
  mutations: {
    setAuthToken (state, token) {  // I use it on the Login
      state.auth.token = token
      state.auth.isUserLoggedIn = !!token
      localStorage.setItem('store', JSON.stringify(state))
    },
    cleanAuth (state) {  // I use it on the Logout
      state.auth = {
        token: '',
        isUserLoggedIn: false
      }
      localStorage.setItem('store', JSON.stringify(state))
    }
  }
  ...
})

EDIT 1:

When I use this.$store.state.auth.isUserLoggedIn explicitly on my code, it works well. So, the button appears and disappears correctly. I give below an example:

Menu.vue > template

<v-toolbar-items class='hidden-sm-and-down'>
    <v-btn v-if='this.$store.state.auth.isUserLoggedIn' flat> 
      Test {{ this.$store.state.auth.isUserLoggedIn }}
    </v-btn>
</v-toolbar-items>

Hence, I believe that the problem is in the binding of showButton with this.$store.state.auth.isUserLoggedIn.


Solution

  • Through the answers of Chris Li, Andrei Gheorghiu and Sajib Khan I could solve my problem.

    Andrei Gheorghiu have explained that I can't access computed properties in data() and Chris Li suggested that I use a computed variable instead. These answers plus Sajib Khan example I was able to think in a solution that I share below. I hope that it help others in the future.

    In a nutshell, I've created a computed property that returns my array and always when this.$store.state.auth.isUserLoggedIn updates, the array changes together (consequently the menu as well).

    I intent to create a mapGetter to my this.$store.state.auth.isUserLoggedIn. As soon as I do it, I update the answer.

    Thank you guys so much.

    <script>
    export default {
      data () {
        return { ... }
      },
      computed: {
        rightMenu () {
          return [
            { title: 'sign_in', to: '/sign_in', 
                showButton: !this.$store.state.auth.isUserLoggedIn },
            { title: 'sign_up', to: '/sign_up', 
                showButton: !this.$store.state.auth.isUserLoggedIn },
            { title: 'sign_out', to: '/sign_out',
                showButton: this.$store.state.auth.isUserLoggedIn }
          ]
        }
      }
    }
    </script>
    

    EDIT 1: Solution using mapGetters

    Menu.vue

    <script>
    import { mapGetters } from 'vuex'
    
    export default {
      data () {
        return { ... }
      },
      computed: {
        ...mapGetters([
          'isUserLoggedIn'
        ]),
        rightMenu () {
          return [
            { title: 'sign_in', to: '/sign_in', 
                showButton: !this.$store.state.auth.isUserLoggedIn },
            { title: 'sign_up', to: '/sign_up', 
                showButton: !this.$store.state.auth.isUserLoggedIn },
            { title: 'sign_out', to: '/sign_out',
                showButton: this.$store.state.auth.isUserLoggedIn }
          ]
        }
      }
    }
    </script>
    

    store.js

    I've added the following getter:

    ...
    getters: {
      isUserLoggedIn (state) {
        return state.auth.isUserLoggedIn
      }
    }
    ...