Lately I've been trying to use VueJS to create a small project and I have encountered this problem. So I have this navigation bar that includes a Log Out Button element that'd only shown if someone has already logged in. This is my Navbar before I logged in.
Before Login Navbar. No Log Out Button
After I logged in, my navbar'd be like this.
after logged in Navbar. Log Out Button exists
This is my code for this navbar component.
<div id="logout-button" v-if="isLoggedIn">
<button v-on:click="logOut">Log Out</button>
</div>
....
<script>
export default {
name: "NavigationBar",
data: function () {
return {
isLoggedIn: false,
};
},
mounted() {
if (localStorage.isLoggedIn !== undefined) {
this.isLoggedIn = localStorage.isLoggedIn;
}
},
methods: {
...
logOut: function () {
localStorage.isLoggedIn = false;
localStorage.access = null;
localStorage.refresh = null;
location.reload();
},
},
};
</script>
What I've been trying is whenever the log out button clicked, I'd change the value of a key in my local storage, named "isLoggedIn" from "true" to "false". After that, I'd reload the page and when the page reached mounted, the component's data named "isLoggedIn"'d be changed with the new value of my local storage which is now "false". Hence, my expectation is the button wouldnt' be there.
The problem is, whenever I clicked log out, the Log Out Button'd be always there as if I didn't change the value that hopefully doesn't evaluated as "true" in the v-if. I don't know which or what is the problem since I'm new to Vue and I really hope you guys could tell me what it is. Thank you very much!
In your logOut() method, you're only changing the value in the localStorage, however the v-if="isLoggedin" is binded to the component's data so you also need to update that.
logOut: function () {
localStorage.isLoggedIn = false;
localStorage.access = null;
localStorage.refresh = null;
this.isLoggedIn = false
location.reload();
},
Additionally, you can only store strings in the localStorage, so you need to evaluate your string to return a boolean for your component's data.
this.isLoggedIn = (localStorage.isLoggedIn === 'true')
Here is a small jsfiddle for you to play with: https://jsfiddle.net/6rv7j5bg/
Try using:
localStorage.setItem("isLoggedIn", false)
and:
localStorage.getItem("isLoggedIn")
and see if it makes any difference!
Related
I have a Vue component which fetches data from a remote API passing it to a child component. When the user clicks a button, the API is called to submit the data. The API returns the updated data object and updates the component with the new data. So far, so good.
But I'm struggling for the error case. If the API call is not successful, I need to "reset" the apiData object to the initial state as received in the mounted() function. Otherwise the user would still see the values she changed but which actually failed to update.
Two apporaches come to my mind:
Refresh the data from the API for the error case
Copy the originally received data to a variable which then will be re-assigned in the error case
Or maybe there is some more "Vue-ish" way to achieve this?
<template>
<some-component v-model="apiData"></some-component>
</template>
data() {
return {
apiData: {}
}
},
mounted() {
this.apiData = ApiService.getData();
},
methods: {
async onSubmit() {
try {
const response = await ApiService.update(this.apiData);
this.apiData = response.data;
} catch (e) {
showErrorNotification();
// How to reset `apiData` to the initial state as in mounted() ???
}
}
}
The API gets an error and does not return us any new results.
If it doesn't return new results, we still have our old data (the data you want)
Aren't we waiting for this?
If so, what's the problem for us?
I have implemented a watch within a Vue component that displays product information. The watch watches the route object of vue-router for a ProductID param to change. When it changes, I want to go get the product details from the back-end API.
To watch the route, I do this in Product.vue:
import { useRoute } from 'vue-router'
export default {
setup() {
const route = useRoute();
async function getProduct(ProductID) {
await axios.get(`/api/product/${ProductID}`).then(..do something here)
}
// fetch the product information when params change
watch(() => route.params.ProductID, async (newID, oldID) => {
await getProduct(newId)
},
//watch options
{
deep: true,
immediate: true
}
)
},
}
The above code works, except that if a user navigates away from Product.vue, for example using the back button to go back to the homepage, the watch is triggered again and tries to make a call to the API using undefined as the ProductID (becaues ProductID param does not exist on the homepage route) e.g. http://localhost:8080/api/product/undefined. This causes an error to be thrown in the app.
Why does the watch trigger when a user has navigated away from Product.vue?
How can this be prevented properly? I can do it using if(newID) { await getProduct(newId) } but it seems counterintuitive to what the watch should be doing anyway.
UPDATE & SOLUTION
Place the following at the top replacing the name for whatever your route is called:
if (route.name !== "YourRouteName") {
return;
}
That will ensure nothing happens if you are not on the route you want to watch.
I ran into the same problem. Instead of watching the current route, use vue-router onBeforeRouteUpdate, which only gets called if the route changed and the same component is reused.
From https://next.router.vuejs.org/guide/advanced/composition-api.html#navigation-guards:
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'
export default {
setup() {
// same as beforeRouteLeave option with no access to `this`
onBeforeRouteLeave((to, from) => {
const answer = window.confirm(
'Do you really want to leave? you have unsaved changes!'
)
// cancel the navigation and stay on the same page
if (!answer) return false
})
const userData = ref()
// same as beforeRouteUpdate option with no access to `this`
onBeforeRouteUpdate(async (to, from) => {
// only fetch the user if the id changed as maybe only the query or the hash changed
if (to.params.id !== from.params.id) {
userData.value = await fetchUser(to.params.id)
}
})
},
}
watch registers the watcher inside an vue-internal, but component-independent object. I think it's a Map. So destroying the component has no effect on the reactivity system.
Just ignore the case where newID is undefined, like you already did. But to prevent wrapping your code in a big if block just use if(newID === undefined)return; at the beginning of your callback. If your ids are always truthy (0 and "" are invalid ids) you can even use if(!newID)return;.
well, in your use case the best approach would be to have a method or function which makes the api call to the server, having watch is not a really good use of it, because it will trigger whenever route changes and you do not want that to happen, what you want is simply get the productID from route and make the api call,
so it can be done with getting the productID in the created or mounted and make the api call!
I try to hide a section if an object is null. The problem is that when I refresh the page, I see that section and is hidden immediately after. I try to use v-show, but the result is the same.
<b-card v-if="terminateContractValues">
export default {
props:['terminateContractValues']
}
The section is hidden after I receive data from server (get request)
You can set default value for the prop
<b-card v-if="terminateContractValues">
export default {
props: {
terminateContractValues: {
default: false
}
},
}
I solve the problem.
Default it was set as object {}.
I replace terminateContractValues: {} with terminateContractValues: ''
I have a side-nav component that is hidden by default using v-show.
A click event in an external component sets a flag in vuex to toggle the side-nav.
How can I set focus to the side-nav component's root div once it's displayed?
I am trying to use this focus-on focus-off technique to hide the side-nav
Maybe something like this:
export default {
watch: {
someVarFromVuex(value) {
// Don't focus unless the value got switched on
if (!value) return;
// Make sure to add the ref to your template.
// You could also use an id.
const $input = this.$refs.input.$el;
// Just in case the input somehow doesn't exist...
if ($input) {
this.$nextTick(() => {
$input.focus();
})
}
},
},
};
Note that if you are actually trying to focus a div, then it will need to have a tabindex.
I am creating a fill in the blank using vue. I have three components, a parent component where I build the question, an input component where I validate and the text component. I have stripped out a lot of the code to try and keep the question relevant, anything commented out is unsuccessful. Hope I am not out in left field on this one but have never attempted this so thought I would try here.
First issue:
Some of the questions have two inputs and I want to auto provide focus to the first input, (using a custom directive ) I am able to gain focus on the last created input. I am not really sure how to access first child I guess. Works well with single input questions though. I have tried doing so by using $refs and $nextTick(()) but no luck.
Second issue:
Once one input gets isCorrect I want to auto focus to the next available non correct element. I figured I would have be able to access the child input from the parent component and have been trying using this link but I have been unsuccessful.
Any help or insights is much appreciated. thanks
What it looks like for visual
What I am after
Parent Component
import ivInput from "../inline-components/iv-input.vue";
import ivText from "../inline-components/iv-text.vue";
<component
:is="buildQuestion(index)"
ref="ivWrap"
v-for="(item, index) in questionParts"
:key="index">
</component>
export default() {
components:{
ivInput,
ivText
},
mounted(){
// console.log(this.$refs.ivWrap)
// console.log(this.$refs.iv)
},
methods: {
buildQuestion: function (index) {
if(this.questionParts[index].includes('*_*')){
return ivInput
}else{
return ivText
}
},
//focus: function (){
// this.$refs.iv.focus()
// console.log(this.$refs.iv)
// }
}
}
Input Component
<div :class="'iv-input-wrap'">
<input
ref="iv"
v-focus
type="text"
v-model="userInput"
:class="[{'is-correct':isCorrect, 'is-error':isError}, 'iv-input']"
:disabled="isCorrect">
</div>
export default{
// directives:{
// directive definition
// inserted: function (el) {
// el.focus()
// },
}
computed{
isCorrect: function () {
if(this.isType == true && this.isMatch == true){
// this.$refs.iv.focus()
// this.$nextTick(() => this.$refs.iv.focus)
return true
}
}
}
}