1

When I login using a form this happens:

  1. loggedIn value in localStorage is changed to "true"
  2. Router pushes to /home
  3. Header doesn't change and still shows Login/Signup buttons

I need it to be

  1. loggedIn value in localStorage is changed to "true"
  2. Router pushes to /home
  3. Header changes and a picture

Header.vue:

      <div class="flex flex-wrap items-center justify-end ">
        <HeaderItem v-if="!isLoggedIn"
            class="pl-10" text = "Login" link="/login"/>
        <HeaderItem v-if="!isLoggedIn" class="pl-10"
                    text = "Signup" link="/signup"/>

        <div v-if="isLoggedIn">
          <UserHeader/>
        </div>
      </div>

export default {
  name: 'App',
  components: {HeaderItem, UserHeader},
  data() {
    return {
      homeLink: "/home"
    }
  },
  created: {
    isLoggedIn() {
      console.log(JSON.parse(localStorage.getItem("loggedIn")) === "true");

      if (localStorage.getItem("loggedIn") === "true")  {
        console.log("STORAGE LOGGED IN TRUE");
      }
      else  {
        console.log("STORAGE LOGGED IN FALSE");
      }

      return localStorage.getItem("loggedIn") === "true";
    }
  }
}

It only prints the correct message and changes header after I press Ctrl+Shift+R. But the localStorage has the correct loggedIn value right away. How do I fix it?

EDIT:

I also tried this:

  <div class="flex flex-wrap items-center justify-end ">
        <HeaderItem v-if="!loggedIn"
            class="pl-10" text = "Login" link="/login"/>
        <HeaderItem v-if="!loggedIn" class="pl-10"
                    text = "Signup" link="/signup"/>

        <div v-if="loggedIn">
          <UserHeader/>
        </div>
      </div>

export default {
  name: 'App',
  components: {HeaderItem, UserHeader},
  data() {
    return {
      homeLink: "/home",
     // loggedIn: false
    }
  },
  computed: {
    loggedIn() {
      return localStorage.getItem("loggedIn") === "true";
    },
...

It has the same results: the header only changes after the page refresh (Ctrl+Shift+R).

EDIT:

I can't set localStorage.loggedIn inside Header! It is set in LoginForm.vue, completely different component

parsecer
  • 4,758
  • 13
  • 71
  • 140
  • $("#header").load("../pages/header.html"); – stackoverflow account Mar 26 '21 at 09:55
  • 1
    *doesn't work* - please provide *some* diagnostic checks. Is there an error in the console `.load is not a function` or `Access-Control-Allow-Origin`. Is there an error in network tab: `404 header.html not found` See https://idownvotedbecau.se/itsnotworking/ – freedomn-m Mar 26 '21 at 10:04
  • That (most likely) means you're opening the `.html` file locally from your computer, eg double-clicking on your desktop. Some js actions (eg ajax) are blocked giving that message when opening an html page this way. You need to "run" your page from a server - eg see this question: https://stackoverflow.com/questions/5050851/best-lightweight-web-server-only-static-content-for-windows – freedomn-m Mar 26 '21 at 11:02

2 Answers2

6

Local storage isn't reactive so VueJS can't detect changes to it. As a result, your computed property is unable to track changes to it

The reason it works on a reload is that computed methods will always run at least once to generate the initial value.

You need to tie the updating of local storage with a reactive variable like a boolean.

Each time you access your local storage object, check the storage value and set a boolean in VueJS - something like isLoggedIn and use that instead of your computed method.

You can either use Vuex and commits, or simply set the state of the header component.

For example, when your component is created you can set isLoggedIn to true or false depending on if the local storage key is present.

Similarly, you can also set isLoggedIn to true when a user logs in (In the same method you set your local storage key) and to false when a user logs out.

Consider the following example:

Header.vue

  <div class="flex flex-wrap items-center justify-end ">
    <HeaderItem v-if="!isLoggedIn"
        class="pl-10" text = "Login" link="/login"/>
    <HeaderItem v-if="!isLoggedIn" class="pl-10"
                text = "Signup" link="/signup"/>

    <div v-if="isLoggedIn">
      <UserHeader/>
    </div>
  </div>

export default {
  name: 'App',
  components: {HeaderItem, UserHeader},
  data() {
    return {
      homeLink: "/home",
      isLoggedIn: false,
    }
  },
  computed: {
      isLoggedIn() {
          return this.$store.state.isLoggedIn;
      }
  },
}

And then in any other component

  created() {
      if (localStorage.getItem("loggedIn") === "true")  {
          this.$store.commit('SET_LOGGED_IN', true);
      }
  },
  methods: {
       login() {
           localStorage.setItem('loggedIn', 'true');
           this.$store.commit('SET_LOGGED_IN', true); 
       }
  }

For the above example, you have a Vuex store with a boolean state called isLoggedIn. You then commit a mutation called SET_LOGGED_IN that sets the state of isLoggedIn to true or false.

Using a computed property in any component, you can easily access the value of isLoggedIn and reactively respond to changes in it.

Every time you update or read from the local storage, you must also update a variable in VueJS. That variable is what is reactive.

Jordan
  • 2,245
  • 6
  • 19
  • Could you please show some code of how would I do that? Where would that variable go? I know how to use vuex's store, but the store is wiped after a page reload... – parsecer Apr 23 '21 at 21:06
  • 1
    Exactly why you're using local storage for the key. Let me update the code give me a couple minutes – Jordan Apr 23 '21 at 21:07
  • I asked around and everyone uses localStorage for user token. – parsecer Apr 23 '21 at 21:08
  • What about this one? https://developer.mozilla.org/en-US/docs/Web/API/Window/storage_event – parsecer Apr 23 '21 at 21:10
  • You can do that too. What you need to do is at the same time as when ytou update local storage, you also update your variable :) – Jordan Apr 23 '21 at 21:11
  • @parsecer for vue.js, you don't need storage_event, the best practice is Bus Event of vue.js. I have updated my code below, give it a try. – Riyaz Khan Apr 23 '21 at 21:16
  • I **can't** set `localStorage.loggedIn` inside `Header`! It is set in **LoginForm.vue**, completely different component – parsecer Apr 23 '21 at 21:28
  • Header **only** has access to vuex or localstorage – parsecer Apr 23 '21 at 21:29
  • That is fine. You just need to use Vuex instead. Inside your LoginForm.vue, you need need to commit to Vuex to set a boolean each time you change local storage. When you login, commit something. When you log out, commit something, etc. Then in your Header you need to just access the Vuex state instead of the isLoggedIn local variable – Jordan Apr 23 '21 at 22:39
  • @parsecer I have updated my example to demonstrate with Vuex instead – Jordan Apr 24 '21 at 10:28
0

If you want your functions to be reactive on the same component, then create a variable and set its value where you set the localStorage loggedIn value.

This is just an example code:

<template>
  <div class="flex flex-wrap items-center justify-end ">
    <h3>{{ isLoggedIn }}</h3>
  </div>
</template>

<script>
  export default {
    data () {
      return {
        loggedIn: false
      }
    },
    created () {
      // added 3 seconds gap to make the value true and check reactivity
      setTimeout(() => this.onSignIn(), 3000)
    },
    computed: {
      
      isLoggedIn() {
        if (localStorage.getItem('loggedIn')) return localStorage.getItem("loggedIn") === "true";
        return this.loggedIn
      }
    },
    methods: {
      onSignIn () {
        this.loggedIn = true
        localStorage.setItem('loggedIn', true)
      }
    }
  }
</script>

Update 2:

After understanding your situation from a previous question similar to this that you asked. Here is the code, that will help you in your case wherein LoginForm.vue you are setting localStorage value and you want to use that value in your App.vue and pass it to Header.vue. For such case, I used the Bus Event in order to communicate between the component which might be far on the parent to access, such as App.vue.

event-bus.js

import Vue from 'vue';
const EventBus = new Vue();
export default EventBus;

LoginForm.vue

<template>
  <div class="home-page">
    <button type="submit" @click="onSignIn()">Login</button>
  </div>
</template>

<script>
import EventBus from '../event-bus';
  export default {
    methods: {
      onSignIn () {
        localStorage.setItem('loggedIn', true)
        EventBus.$emit('OnLogin', true)
      }
    }
  }
</script>

App.vue

<template>
  <div id="app">
    <Header :isLoggedIn="isLoggedIn"/>
    <router-view/>
  </div>
</template>
<script>
import Header from './components/Header'
import EventBus from './event-bus';
export default {
  components: {
    Header
  },
  data() {
    return {
      loggedIn: false
    }
  },
  computed: {
    isLoggedIn() {
      if (localStorage.getItem('loggedIn')) return localStorage.getItem("loggedIn") === "true";
      return this.loggedIn
    }
  },
  created () {
    EventBus.$on('OnLogin', (isLogin) => {
      this.loggedIn = isLogin
    })
  }
}
</script>

Header.vue

<template>
  <div class="flex flex-wrap items-center justify-end ">
    <HeaderItem v-if="!isLoggedIn"
        class="pl-10" text = "Login" link="/login"/>
    <HeaderItem v-if="!isLoggedIn" class="pl-10"
                text = "Signup" link="/signup"/>

    <div v-if="isLoggedIn">
      <UserHeader/>
    </div>
  </div>
</template>

<script>
import HeaderItem from './HeaderItem'
import UserHeader from './UserHeader'
  export default {
    components: {HeaderItem, UserHeader},
    props: ['isLoggedIn']
  }
</script>
Riyaz Khan
  • 2,765
  • 2
  • 15
  • 29