When I login using a form this happens:
loggedIn value in localStorage is changed to "true"
Router pushes to /home
Header doesn't change and still shows Login/Signup buttons
I need it to be
loggedIn value in localStorage is changed to "true"
Router pushes to /home
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
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.
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>
Related
I tried this.$forceupdate() , v-if hack but it didn't work. I am new to Vue.
<template>
<div class="intro-y grid grid-cols-12 gap-3 sm:gap-6 mt-5">
<HeroCard v-for="hero in heroes" :key="hero.id" :hero="hero" />
</div>
</template>
<script>
import HeroCard from "#/components/hero/HeroCard.vue";
export default {
inject: ["heroStats"],
name: "HeroList",
components: {
HeroCard,
},
data() {
return {
heroes: this.heroStats,
};
},
methods: {
filterHeroes(heroStats, primary_attribute, attack_type, roles, name) {
if (!primary_attribute.length) {
this.heroes = heroStats;
} else {
this.heroes = heroStats.filter((hero) =>
primary_attribute.includes(hero.primary_attr)
);
...etc
}
},
},
};
</script>
When Checkboxes are checked the HeroCard component should display heroes that including the primary attributes[ 'Strength', 'Intelligence' ]
I would make heroes to a computed property and filter it with a selected_attributes data property bound to your checkboxes.
data() {
return {
heroStats: heroStats,
selected_attributes: ['Strength', 'Intelligence']
}
},
computed: {
heroes() {
return this.heroStats.filter((hero) =>
this.selected_attributes.includes(hero.primary_attr)
);
}
}
Then the heroes list will be auto-updated, when the checkboxes and selected_attributes changes. This ten will automatically trigger the update of your heroes card list.
<HeroCard v-for="hero in heroes" :key="hero.id" :hero="hero" />
This is the simplest and most Vue style solution from my point of view.
The Vue reactivity do all the work and you don't need to trigger update of your child components.
you can set a flag and when the Object Array changes set that flag to true (visa versa)
and then use a v-if to render that component only when that flag is set to true
something like
<div class="intro-y grid grid-cols-12 gap-3 sm:gap-6 mt-5">
<HeroCard v-if="showComponent" v-for="hero in heroes" :key="hero.id" :hero="hero" />
</div>
</template>```
I got this component:
<template>
<Popover v-slot="{ open }">
<PopoverButton>
{{ title }}
</PopoverButton>
<div v-if="computedOpen">
<PopoverPanel static>
<slot name="popover"></slot>
</PopoverPanel>
</div>
</Popover>
</template>
<script>
import {Popover, PopoverButton, PopoverPanel} from '#headlessui/vue'
import {computed, ref, watch} from 'vue'
import {useRoute} from 'vue-router'
export default {
name: 'DropdownMenuButton',
mixins: [slots],
props: {
name: {
type: String,
},
title: {
type: String,
default: ''
},
},
components: {
Popover,
PopoverButton,
PopoverPanel,
ChevronDownIcon,
},
setup(props) {
const isOpen = ref(null);
const route = useRoute()
watch(route, () => {
isOpen.value = false;
});
const computedOpen = computed(() => {
let open = ...? //this is missing...
return isOpen.value && open.value;
});
return {
computedOpen
}
},
}
</script>
This component makes use of headless UI's popover.
Now I'd like to close the popover once the route changes. While the route-change is being detected fine, I can not access the <Popover>'s open value in the setup() method to determine, whether computedOpen should return true or false.
My question: How can I access v-slot="{ open } in the computed value?
What you want is not possible.
Think about it:
Everything inside <Popover> element (the slot content) is compiled by Vue to a function returning the virtual DOM representation of the content
This function is passed to the Popover component as slots.default and when the Popover component is rendering, it calls that function passing the open value as an argument
So open value is Popover component's internal state and is only accessible inside the template (ie. slot render function)
So I think your best bet is throw away the computed idea and just use the open value directly in the template or use method instead, passing open as an argument
<div v-if="isOpen && open">
<PopoverPanel static>
<slot name="popover"></slot>
</PopoverPanel>
</div>
<div v-if="isPanelOpen(open)">
<PopoverPanel static>
<slot name="popover"></slot>
</PopoverPanel>
</div>
I have a Login Page on which the user has to authorize and it is working.
Then the user is being redirected to the next page called Dashboard and I want to get his user profile details from the different endpoint, using Vuex.
I can see in the console, that data is retrieved immediately after the page is loaded, but it's not displayed on the page, I need to refresh the page to load the data. Can someone help me to figure out how to get rid of the page reload and put the data there automatically?
First, under created() I am using dispatch to get the data, and then in the computed present it.
Here is my code first Dashbaord.vue and user.module.js:
import HeaderBar from "#/components/header/HeaderBar.vue";
export default {
name: "Dashboard",
components: { HeaderBar },
created() {
this.$store.dispatch("user_account/getUserDetails");
console.log("DASHBOARD: Created");
console.log(this.$store.status);
},
computed: {
currentUser() {
console.log("Computed");
console.log(this.$store.state);
return this.$store.state.user_account;
},
},
};
.dashboard {
h4 {
line-height: 18px;
}
}
<template>
<div>
<main role="main">
<div
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3"
>
<h1 class="h1">Hi, {{ currentUser.first_name }}</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group mr-2">
Search will be here added
</div>
</div>
</div>
</main>
<div class="mt-4 mb-5">
<HeaderBar />
</div>
<main role="main">
<div class="row mb-2">
</div>
</main>
</div>
</template>
import UserService from "../services/user.service";
const user = JSON.parse(localStorage.getItem("user"));
console.log("USER MODULE: ")
console.log(user)
const initialState = user
? user
: null;
console.log(initialState)
export const user_account = {
namespaced: true,
state: initialState,
actions: {
async getUserDetails({commit}) {
return UserService.getUserDetails().then(
(user) => {
commit("getUserDetailsSuccess", user);
return Promise.resolve(user);
},
(error) => {
commit("getUserDetailsFailure");
return Promise.reject(error);
}
);
}
},
mutations: {
getUserDetailsSuccess(state, user) {
state.user = user;
},
getUserDetailsFailure(state) {
state.user = null;
}
}
};
user_account/getUserDetailsSuccess sets state.user, but your computed prop does not return .user.
It should look like this:
export default {
created() {
this.$store.dispatch('user_account/getUserDetails')
},
computed: {
currentUser() {
// return this.$store.state.user_account ❌
return this.$store.state.user_account.user
},
},
}
demo
My component template:
<template>
<section class="stage my-5">
<div class="stage-heading">
<h3 class="stage-number mb-4">Stage {{stage}}</h3>
<h6 class="stage-hrs">Total Hrs: {{totalHours}}</h6>
</div>
<div class="stage-courses card text-white bg-info mb-3" v-for="course in courses" :key="course.id">
<div class="card-header">Stage {{course.stage}}</div>
<div class="card-body">
<h5 class="card-title">{{course.title}}</h5>
<p class="card-text">{{course.creator}}</p>
<p class="card-text">{{course.hours}} Hours</p>
</div>
</div>
</section>
</template>
The state in my Vuex store:
const state = {
roadmapStage1: [],
roadmapStage2: [],
roadmapStage3: [],
};
I have getters in my Vuex store that look like:
getRoadmapStage1: state => state.roadmapStage1,
getRoadmapStage2: state => state.roadmapStage2,
getRoadmapStage3: state => state.roadmapStage3,
I'm trying to dynamically call one of these getters from a component, which one depends on a prop of the component:
export default {
name: "Stage",
data() {
return {
courses: []
}
},
props: ['stage'],
computed: mapGetters({courses: 'getRoadmapByStage'})
}
Is there any way to insert the prop into the 'getRoadmapByStage'? e.g. so it functions like
getRoadmapByStage${stage}?
Ultimately i'm trying to get the component to re-render anytime one the roadmapStage arrays are updated. Thanks!
I would suggest using a getter with a parameter for the stage id/number that returns the roadmap you want, like so:
// in getters.js
//gets the current roadmap based on the stage number
getRoadmapByStage: (state) => (stageNumber) => {
return state["roadmapStage" + stageNumber];
}
now in your component you can have:
computed: {
currentRoadmap() {
// now we'll pass in your 'stage' prop to get the appropriate map
// this will re-render the component as that prop changes
return this.$store.getters.getRoadmapByStage(this.stage);
}
}
You can declare your computed roadmap property as follows:
computed: {
roadmap() {
return this.stage ? this.$store.getters['getRoadmapByStage' + this.stage] : undefined
},
}
That way you are getting the roadmap by the value of the prop or undefined if the prop is not set to anything.
I am making a chatbot in vue.js and I need your help. I created 2 Vue components:
ChatLoader.vue - first components that render a button to open actual webchat window
Webchat.vue - the async component that only loads when I
Click on a button to open the chat window.
So what my ChatLoader.vue is doing is setting parameter chatInitialized = true on button click. Then the chat window is opened.
In my Webchat.vue I have a close button which on click only hides the chat window (not removed from DOM) by setting showWindow = false;
Now when the chat window is hidden I again see the button to open the chat (which was there all the time only not visible because overlapped by chatwindow) but when I click on the button now I want to set showWindow = true in Webchat.vue component instead of the previous behavior, so the webchat window is shown again.
ChatLoading.vue:
<template>
<div>
<span class="open-chat" v-on:click="showChat">
<i class="icon ficon-live-chat"></i>
Virtual assistant
</span>
<Webchat v-if="chatInitialized"></Webchat>
</div>
</template>
<script>
import ChatLoading from "./ChatLoading.vue";
const Webchat = () => ({
component: import('./Webchat.vue'),
loading: ChatLoading
});
export default {
data() {
return {
chatInitialized: false
}
},
components: {
Webchat
},
methods: {
showChat() {
this.chatInitialized = true;
}
}
}
</script>
Webchat.vue:
<template>
<div class="chat-window" v-show="showWindow">
<button type="button" class="cancel icon ficon-close" v-on:click="minimize"></button>
<WebchatPlugin
>
</<WebchatPlugin>
</div>
</template>
<script>
import <WebchatPlugin{
createDirectLine,
createStore
} from "botframework-webchat/lib/index";
import {DirectLine} from "botframework-directlinejs";
export default {
data() {
return {
showWindow : true
}
},
components: <WebchatPlugin
methods: {
minimize() {
this.showWindow = false
}
},
</script>
How can I accomplish that? Thank you
If you want to toggle the child component's (<Webchat>) state showWindow from a consuming parent component, then you will have to create a method in the child component that can be invoked by the parent element.
First of all, in your Webchat component, create a new method, say maximize, that will change this.showWindow to true:
methods: {
minimize() {
this.showWindow = false;
},
maximize() {
this.showWindow = true;
}
},
Then, in your parent component, you can then:
Create a reference to your Webchat component
Use this.$ref to access the component and its inner methods, and call the maximize() method you've just created:
Example:
<template>
<div>
<span class="open-chat" v-on:click="showChat">
<i class="icon ficon-live-chat"></i>
Virtual assistant
</span>
<!-- Use `ref` attribute to create a reference to component -->
<Webchat ref="webchat" v-if="chatInitialized"></Webchat>
</div>
</template>
<script>
import ChatLoading from "./ChatLoading.vue";
const Webchat = () => ({
component: import('./Webchat.vue'),
loading: ChatLoading
});
export default {
data() {
return {
chatInitialized: false
}
},
components: {
Webchat
},
methods: {
showChat() {
this.chatInitialized = true;
// Access Webchat component's inner method
// Do this inside `this.$nextTick` to ensure it is accessible
this.$nextTick(() => {
this.$refs.webchat.maximize();
});
}
}
}
</script>