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
Related
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>
I am trying to develop a discussion forum website using React, Node and MongoDB.In post object, there is nested author object and tags array.
Here is sample image of a post object:
here is the component which I am trying to render:
import React, { Component } from "react";
import http from "../services/httpService";
import { postEndPoint, repliesEndPoint } from "../config.json";
class PostPage extends Component {
state = {
post: [],
replies: [],
};
async componentDidMount() {
const id = this.props.match.params.id;
const { data: post } = await http.get(postEndPoint + "/" + id);
const { data: replies } = await http.get(repliesEndPoint + "/" + id);
console.log(post.tags, typeof post.tags);
this.setState({ post: post, replies: replies });
}
render() {
const { post, replies } = this.state;
return (
<React.Fragment>
<div className="container col-lg-8 shadow-lg p-3 mt-5 bg-body rounded">
<h2>{post.title}</h2>
<p className="mt-4" style={{ color: "#505050" }}>
{post.description}
</p>
<div className="mt-1">
Related Topics:
{post.tags.map((tag) => (
<span className="badge badge-secondary m-1 p-2">
{(tag).name}
</span>
))}
<h6 className="mt-2">
{post.upvotes.length} Likes {post.views} Views
</h6>
<div class="d-flex w-100 justify-content-between">
<small class="mb-1">Posted by {post.author['name']}</small>
</div>
</div>
</div>
</React.Fragment>
);
}
}
export default PostPage;
This throws the following : TypeError: post.tags is undefined. a Similar error is throws while accessing post.upvotes and post.author
Since you do your http request in 'componentDidMount' a render occured at least once before. So react tried to read post.something and it was still undefined.
And even if you do it before an http request is asynchronous so be careful
You need to check that post.something is defined before you use.
Also your initialisation if confusing you initialize post as an array but you are trying to do post.title.
If post is really an array then post.map() won't crash on an empty array.
If it's an object check that is it defined correctly.
Try this as initial state
state = {
post: {
description:"",
title:"",
tags: [],
author:[] ,
upvotes:[] ,
views : 0
},
}
initial state for post is {}
state = {
post: { tags: [] },
replies: [],
};
You can have a simple if condition added. So it will only loop through that if it is present. Check this.
import React, { Component } from "react";
import http from "../services/httpService";
import { postEndPoint, repliesEndPoint } from "../config.json";
class PostPage extends Component {
state = {
post: [],
replies: [],
};
async componentDidMount() {
const id = this.props.match.params.id;
const { data: post } = await http.get(postEndPoint + "/" + id);
const { data: replies } = await http.get(repliesEndPoint + "/" + id);
console.log(post.tags, typeof post.tags);
this.setState({ post: post, replies: replies });
}
render() {
const { post, replies } = this.state;
return (
<React.Fragment>
<div className="container col-lg-8 shadow-lg p-3 mt-5 bg-body rounded">
<h2>{post.title}</h2>
<p className="mt-4" style={{ color: "#505050" }}>
{post.description}
</p>
<div className="mt-1">
Related Topics:
{post.tags && post.tags.map((tag) => ( // <--- map will only execute when it finds tags.
<span className="badge badge-secondary m-1 p-2">
{(tag).name}
</span>
))}
<h6 className="mt-2">
{(post.upvotes && post.upvotes.length) || 0} Likes {post.views} Views // <---- These default values too will handle the case where the data isnt ready yet
</h6>
<div class="d-flex w-100 justify-content-between">
<small class="mb-1">Posted by {post.author['name']}</small>
</div>
</div>
</div>
</React.Fragment>
);
}
}
export default PostPage;
I'm building an application to power the backend of a website for a restaurant chain. Users will need to edit page content and images. The site is fairly complex and there are lots of nested pages and sections within those pages. Rather than hardcode templates to edit each page and section, I'm trying to make a standard template that can edit all pages based on data from the route.
I'm getting stuck on the v-model for my text input.
Here's my router code:
{
path: '/dashboard/:id/sections/:section',
name: 'section',
component: () => import('../views/Dashboard/Restaurants/Restaurant/Sections/Section.vue'),
meta: {
requiresAuth: true
},
},
Then, in my Section.vue, here is my input with the v-model. In this case, I'm trying to edit the Welcome section of a restaurant. If I was building just a page to edit the Welcome text, it would work no problem.:
<vue-editor v-model="restInfo.welcome" placeholder="Update Text"></vue-editor>
This issue is that I need to reference the "welcome" part of the v-model dynamically, because I've got about 40 Sections to deal with.
I can reference the Section to edit with this.$route.params.section. It would be great if I could use v-model="restInfo. + section", but that doesn't work.
Is there a way to update v-model based on the route parameters?
Thanks!
Update...
Here is my entire Section.vue
<template>
<div>
<Breadcrumbs :items="crumbs" />
<div v-if="restInfo">
<h3>Update {{section}}</h3>
<div class="flex flex-wrap">
<div class="form__content">
<form #submit.prevent>
<vue-editor v-model="restInfo.welcome" placeholder="Update Text"></vue-editor>
<div class="flex">
<button class="btn btn__primary mb-3" #click="editText()">
Update
<transition name="fade">
<span class="ml-2" v-if="performingRequest">
<i class="fa fa-spinner fa-spin"></i>
</span>
</transition>
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import { VueEditor } from "vue2-editor"
import Loader from '#/components/Loader.vue'
import Breadcrumbs from '#/components/Breadcrumbs.vue'
export default {
data() {
return {
performingRequest: false,
}
},
created () {
this.$store.dispatch("getRestFromId", this.$route.params.id);
},
computed: {
...mapState(['currentUser', 'restInfo']),
section() {
return this.$route.params.section
},
identifier() {
return this.restInfo.id
},
model() {
return this.restInfo.id + `.` + this.section
},
crumbs () {
if (this.restInfo) {
let rest = this.restInfo
let crumbsArray = []
let step1 = { title: "Dashboard", to: { name: "dashboard"}}
let step2 = { title: rest.name, to: { name: "resthome"}}
let step3 = { title: 'Page Sections', to: { name: 'restsections'}}
let step4 = { title: this.$route.params.section, to: false}
crumbsArray.push(step1)
crumbsArray.push(step2)
crumbsArray.push(step3)
crumbsArray.push(step4)
return crumbsArray
} else {
return []
}
},
},
methods: {
editText() {
this.performingRequest = true
this.$store.dispatch("updateRest", {
id: this.rest.id,
content: this.rest
});
setTimeout(() => {
this.performingRequest = false
}, 2000)
}
},
components: {
Loader,
VueEditor,
Breadcrumbs
},
beforeDestroy(){
this.performingRequest = false
delete this.performingRequest
}
}
</script>
Try to use the brackets accessor [] instead of . :
<vue-editor v-model="restInfo[section]"
I am using regular Vue.js in my project. I store data in a store made from scratch and use it in a template:
<template>
<div>
<div class="row">
<div v-for="(picture, index) in storeState.pictures"
:key="index"
class="col-md-2 my-auto">
<div >
<img class="img-thumbnail img-responsive"
:src="picture.url"
#click="deleteMe">
</div>
</div>
</div>
</div>
</template>
<script>
import { store } from "../common/store.js"
export default {
name:"PictureUpload",
data() {
return {
storeState: store.state,
};
},
methods: {
deleteMe() {
let apiUrl = this.picture.url
console.log(apiUrl)
}
}
}
</script>
My pictures are rendering well but now I want to add a delete() function to the picture #click and whenever I click on the button I get:
TypeError: Cannot read property 'url' of undefined
So how can I access my picture data inside my method?
You should pass picture as parameter in the click handler :
#click="deleteMe(picture)">
and refer to it in the method like :
methods: {
deleteMe(picture) {
let apiUrl = picture.url //omit this
console.log(apiUrl)
}
}
the storeState should be a computed property :
export default {
name:"PictureUpload",
data() {
return {
};
},
computed:{
storeState(){
return store.state;
}
},
methods: {
deleteMe(picture) {
let apiUrl = picture.url
console.log(apiUrl)
}
}
}
Currently, whenever a user logs in using correct credentials, a token get saved in the local storage. Now I'm trying to hide signin and signup after the user logs in.
The code I currently have works relatively alright, however, I noticed that when the user logs in, the signin and signup routes do not disappear until the page is refreshed which is not very SPA like.
Why is this happening?
<template>
<div class="nav-header">
<div class="wrapper">
<ul class='nav-ul'>
<router-link to="/" tag='li' active-class='active' exact><li><a>Home</a></li></router-link>
<router-link to="/signup" v-if="!isLoggedIn" tag='li' active-class='active' exact><li><a>Sign Up</a></li></router-link>
<router-link to="/signin" v-if="!isLoggedIn" tag='li' active-class='active' exact><li><a>Sign In</a></li></router-link>
</ul>
</div>
</div>
</template>
<script>
export default {
computed: {
isLoggedIn() {
return !!window.localStorage.getItem('token')
}
}
}
</script>
App.vue
<template>
<div id="app">
<app-header></app-header>
<router-view></router-view>
</div>
</template>
<script>
import Header from './components/header.vue';
export default {
components: {
appHeader: Header
}
}
</script>
Sigin.vue
<template>
<div>
<input v-model="email" placeholder="Your Email...">
<input v-model="password" placeholder="Your Password...">
<button v-on:click.prevent="signin" type="submit" name="submit">Submit</button>
</div>
</template>
<script>
import axios from 'axios'
axios.defaults.baseURL = 'http://94.155.24.68/api';
export default {
data: function() {
return {
email: '',
password: ''
}
},
methods: {
signin: function(){
axios.post('/signin', {
email: this.email,
password: this.password
})
.then((response) => {
console.log(response);
const token = response.data.token;
localStorage.setItem('token', token);
}).catch((error) => console.log(error));
}
}
}
</script>
The reason this doesn't work is because you are trying to watch for changes on the non reactive localStorage.
To make it reactive I tend to create a global Vue instance using Vue.prototype (allowing you to use it in all your components)
Vue.prototype.$localStorage = new Vue({
data: {
// token property returning the ls token value
token: window.localStorage.getItem('token')
},
watch:{
// watcher listening for changes on the token property
// to ensure the new value is written back to ls
token(value){ window.localStorage.setItem('token', value) }
}
})
// you can now access the token in all your components using
// this.$localStorage.token get the token value
// this.$localStorage.token = 'tkn'; set the token value
Demo https://codepen.io/jakob-e/pen/LMJEYV?editors=1010
To implement it in your solution you can do:
// in the header component
computed: {
isLoggedIn(){ return this.$localStorage.token !== ''; }
}
// in the signin component
signin(){
axios.post('/signin', {
email: this.email,
password: this.password
})
.then((response) => {
console.log(response);
const token = response.data.token;
this.$localStorage.token = token;
})
.catch((error) => console.log(error));
}