I am trying to create a user Profile form where you can edit user's information such as name and age. I have 2 routes set up, the /which is for the main user component and the /edit which leads to the user Edit component. I have a user state that i am looping over to output name and age for a user in my User Component. I have added a method in my User component named enterEditMode which on click fetches name and age property of the user selected and then outputs that into the form in my EditMode Component. I am trying to create a method which onClick would update the name or age of the user. So i'd like to click on Sam and on the next page, update his name to Samuel and then click on Update Info button which should update the name Sam to Samuel on my User component page.
But i am having a hard time figuring out how i will do it.
Please check this complete working Example.
This is my Vuex Store:-
state: {
user: [{ name: "Max", age: 29 }, { name: "Sam", age: 28 }],
name: "",
age: null
},
getters: {
getUser: state => state.user,
getName: state => state.user.name,
getAge: state => state.user.age
},
mutations: {
setName(state, payload) {
state.name = payload;
},
setAge(state, payload) {
state.age = payload;
}
}
This is my User Component:-
<template>
<v-container>
<v-flex v-for="(user,index) in getUser" :key="index">
{{ user.name }} {{user.age}}
<v-icon #click="enterEditMode(index)">create</v-icon>
</v-flex>
</v-container>
</template>
<script>
import { mapGetters, mapMutations } from "vuex";
export default {
name: "User",
computed: {
...mapGetters({
getUser: "getUser"
})
},
methods: {
...mapMutations({
setName: "setName",
setAge: "setAge"
}),
enterEditMode(index) {
this.setName(this.getUser[index].name);
this.setAge(this.getUser[index].age);
this.$router.push({ name: "EditMode", params: { index: index } });
}
}
};
</script>
This is my EditMode Component:-
<template>
<v-card>
<v-text-field solo v-model="name"></v-text-field>
<v-text-field solo v-model="age"></v-text-field>
<v-btn>Update Info</v-btn>
</v-card>
</template>
<script>
import { mapGetters } from "vuex";
export default {
computed: {
...mapGetters({
getName: "getName",
getAge: "getAge"
}),
name: {
get() {
return this.getName;
},
set(val) {
return this.$store.commit("setName", val);
}
},
age: {
get() {
return this.getAge;
},
set(val) {
return this.$store.commit("setAge", val);
}
}
},
methods: {
updateInfo() {
this.$router.push('/')
}
}
};
</script>
Thank you for all the help guys. Thank you.
You need to create a mutation in the store to update the user list. For instance to update the selected user name:
Create a updateUserName mutation, and make sure the payload contains both the user index and name to be updated:
mutations: {
updateUserName(state, payload) {
state.user[payload.index].name = payload.name;
}
}
And then in the EditMode.vue file, let the set method of computed name to commit the updateUserName mutation we just created, keep in mind to pass in both the index and name properties:
name: {
get() {
return this.getName;
},
set(val) {
return this.$store.commit("updateUserName", {
name: val,
index: this.index
});
}
}
Here index is another computed property taken from the route parameters for convenience:
index() {
return this.$route.params.index;
},
Check the CodeSandbox working example.
Related
So i have a Vuex Store setup which returns me headers and desserts. Inside my desserts i have a property setup named display which is initially false. I have a component Row which i am importing inside my parent component named Table. Row Components accepts a couple of props i.e. Name and Display. The display prop is what is returned from desserts from the vuex store. I am trying to add a mutation such that on Click of the icon in my Row component, the display.laptop can be toggled to true and false. I have setup the toggleLaptopDisplay but i keep getting cannot read property laptop of undefined.
Please look at the complete CodeSandbox.
Here is the complete code:
The Vuex Store:-
export default new Vuex.Store({
state: {
headers: [{ text: "Dessert (100g serving)", value: "name" }],
desserts: [
{ name: "Lollipop", display: { laptop: true } },
{ name: "Marshamallow", display: { laptop: false } }
]
},
getters: {
getHeaders: state => state.headers,
getDesserts: state => state.desserts
},
mutations: {
toggleLaptopDisplay(state) {
state.desserts.display.laptop = !state.desserts.display.laptop;
}
}
});
This is the Table Component:-
<template>
<v-data-table :headers="getHeaders" :items="getDesserts" hide-actions select-all item-key="name">
<template v-slot:headers="props">
<tr>
<th v-for="header in props.headers" :key="header.text">{{ header.text }}</th>
</tr>
</template>
<template v-slot:items="props">
<tr>
<Row :name="props.item.name" :display="props.item.display"/>
</tr>
</template>
</v-data-table>
</template>
<script>
import { mapGetters } from "vuex";
import Row from "./Row";
export default {
components: {
Row
},
data() {
return {};
},
computed: {
...mapGetters({
getHeaders: "getHeaders",
getDesserts: "getDesserts"
})
}
};
</script>
This is the Row component:-
<template>
<div>
{{name}}
<v-icon #click="toggleLaptopDisplay" :color="display.laptop ? 'info': '' ">smartphone</v-icon>
</div>
</template>
<script>
import { mapMutations } from "vuex";
export default {
props: {
name: String,
display: Object
},
methods: {
...mapMutations({
toggleLaptopDisplay: "toggleLaptopDisplay"
})
}
};
</script>
Any help will be appreciated. Thank you :)
Few things to achieve what you want:
In your Row.vue add a method to select appropriate element:
<v-icon #click="toggleChange" :color="display.laptop ? 'info': '' ">smartphone</v-icon>
then in methods, create a method that will pass name of the element as payload:
methods: {
toggleChange() {
this.toggleLaptopDisplay(this.name)
},
...mapMutations({
toggleLaptopDisplay: "toggleLaptopDisplay"
})
}
finally, in store.js, use the payload and mutate selected element:
mutations: {
toggleLaptopDisplay(state, payload) {
state.desserts.find(dessert => dessert.name === payload).display.laptop = !state.desserts.find(dessert => dessert.name === payload).display.laptop
}
}
Edited Example
I've got a problem with state in component. I have websocket and over it come changes, which I put it in some state. It's ok in one component, it dynamically changes value. But, when I go to the next component (vue-router). Its state changes as well, but is not dynamic. hmmmmm... in console.log changes coming, but not change value in another component.
How can I make it?
Let's see some code:
Here my action, with change states
actions: {
play(ctx, array){
axios.get('http://localhost/task_run?task_id='+array.id)
var conn = new WebSocket('ws://localhost:8080', "protocol");
conn.onmessage = function (ev) {
ctx.commit('procent', {key:array.key, val:ev.data});
ctx.commit('procentOne', {key:array.key, val:ev.data});
console.log('Message: ', ev);
};
},
},
mutations: {
procent(state, val){
var array = JSON.parse(val.val);
state.process[val.key] = array.procent;
state.processOnePersone[array.comp] = array.procent;
}
},
state: {
process: [],
processOnePersone:[],
},
getters: {
process(state){
return state.process
},
processOnePersone(state){
return state.processOnePersone;
}
}
I have one compenent, where it works
<v-progress-circular
:rotate="-90"
:size="50"
:width="5"
:value="process[key]"
color="primary"
>
{{ process[key] }}
</v-progress-circular>
<script>
import {mapGetters} from 'vuex';
export default {
name: 'taskListComponent',
computed: {
...mapGetters(['process',]),
},
}
And component where it doesn't work
<v-progress-circular
:rotate="-90"
:size="50"
:width="5"
:value="processOnePersone[key]"
color="primary"
>
{{ processOnePersone[key] }}
</v-progress-circular>
<script>
import {mapGetters} from 'vuex';
export default {
name: 'queueComponent',
computed: {
...mapGetters(['processOnePersone',]),
},
}
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.
Use computed property to make it reactive:
<template>
...
<v-btn v-for='item in rightMenu' :key='item.title'
:to='item.to' v-if='isUserLoggedIn(item.title)' flat>
{{ item.title }}
</v-btn>
...
</template>
<script>
...
computed: {
isUserLoggedIn() {
return (title) => { // you'll not have any caching benefits
if (title === 'sign out') {
return this.$store.state.auth.isUserLoggedIn;
}
return !this.$store.state.auth.isUserLoggedIn;
}
}
}
...
</script>
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
}
}
...
I'm new to Vue and I wanted to learn Veux by building a simple CRUD application with firebase. So far I've been able to figure things out (though if you see something badly coded, I would appreciate any feedback) but I can't seem to figure out how to remove an item. The main problem is that I can't reference it properly. I'm getting [object Object] in my reference path but when I log it I get the correct id.
Firebase Flow:
So I'm making a reference to 'items', Firebase generates a unique key for each item and I added in an id to be able to reference it, though I could also reference it by the key.
I've been able to do this without using Veux and just component state but I've been trying for hours to figure out what I'm doing wrong.
I'm also getting this error:
Store.js
import Vue from 'vue'
import Vuex from 'vuex'
import database from './firebase'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
items: []
},
mutations: {
RENDER_ITEMS(state) {
database.ref('items').on('value', snapshot => {
state.items = snapshot.val()
})
},
ADD_ITEM(state, payload) {
state.items = payload
database.ref('items').push(payload)
},
REMOVE_ITEM(index, id) {
database.ref(`items/${index}/${id}`).remove()
}
},
// actions: {
// }
})
Main.vue
<template>
<div class="hello">
<input type="text" placeholder="name" v-model="name">
<input type="text" placeholder="age" v-model="age">
<input type="text" placeholder="status" v-model="status">
<input type="submit" #click="addItem" />
<ul>
<li v-for="(item, index) in items" :key="index">
{{ item.name }}
{{ item.age }}
{{ item.status }}
<button #click="remove(index, item.id)">Remove</button>
</li>
</ul>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
import uuid from 'uuid'
export default {
name: 'HelloWorld',
created() {
this.RENDER_ITEMS(this.items)
},
data() {
return {
name: '',
age: '',
status: '',
id: uuid(),
}
},
computed: {
...mapState([
'items'
])
},
methods: {
...mapMutations([
'RENDER_ITEMS',
'ADD_ITEM',
'REMOVE_ITEM'
]),
addItem() {
const item = {
name: this.name,
age: this.age,
status: this.status,
id: this.id
}
this.ADD_ITEM(item)
this.name = ''
this.age = ''
this.status = ''
},
remove(index, id) {
console.log(index, id)
this.REMOVE_ITEM(index, id)
}
}
}
</script>
The first argument to your mutation is always the state.
In your initial code:
REMOVE_ITEM(index, id) {
database.ref(`items/${index}/${id}`).remove()
}
index is the state object, which is why you are getting [object Object] in the url.
To fix your issue, pass an object to your mutation and change it to:
REMOVE_ITEM(state, {index, id}) {
database.ref(`items/${index}/${id}`).remove()
}
And when you are calling your mutation with the remove method, pass an object as well:
remove(index, id) {
console.log(index, id)
// pass an object as argument
// Note: {index, id} is equivalent to {index: index, id: id}
this.REMOVE_ITEM({index, id})
}
I'm building a VueJS component which needs to update the data attributes when a prop is updated however, it's not working as I am expecting.
Basically, the flow is that someone searches for a contact via an autocomplete component I have, and if there's a match an event is emitted to the parent component.
That contact will belong to an organisation and I pass the data down to the organisation component which updates the data attributes. However it's not updating them.
The prop being passed to the organisation component is updated (via the event) but the data attibute values is not showing this change.
This is an illustration of my component's structure...
Here is my code...
Parent component
<template>
<div>
<blink-contact
:contact="contact"
v-on:contactSelected="setContact">
</blink-contact>
<blink-organisation
:organisation="organisation"
v-on:organisationSelected="setOrganisation">
</blink-organisation>
</div>
</template>
<script>
import BlinkContact from './BlinkContact.vue'
import BlinkOrganisation from './BlinkOrganisation.vue'
export default {
components: {BlinkContact, BlinkOrganisation},
props: [
'contact_id', 'contact_name', 'contact_tel', 'contact_email',
'organisation_id', 'organisation_name'
],
data () {
return {
contact: {
id: this.contact_id,
name: this.contact_name,
tel: this.contact_tel,
email: this.contact_email
},
organisation: {
id: this.organisation_id,
name: this.organisation_name
}
}
},
methods: {
setContact (contact) {
this.contact = contact
this.setOrganisation(contact.organisation)
},
setOrganisation (organisation) {
this.organisation = organisation
}
}
}
</script>
Child component (blink-organisation)
<template>
<blink-org-search
field-name="organisation_id"
:values="values"
endpoint="/api/v1/blink/organisations"
:format="format"
:query="getQuery"
v-on:itemSelected="setItem">
</blink-org-search>
</template>
<script>
export default {
props: ['organisation'],
data() {
return {
values: {
id: this.organisation.id,
search: this.organisation.name
},
format: function (items) {
for (let item of items.results) {
item.display = item.name
item.resultsDisplay = item.name
}
return items.results
}
}
},
methods: {
setItem (item) {
this.$emit('organisationSelected', item)
}
}
}
</script>
How can I update the child component's data properties when the prop changes?
Thanks!
Use a watch.
watch: {
organisation(newValue){
this.values.id = newValue.id
this.values.search = newValue.name
}
}
In this case, however, it looks like you could just use a computed instead of a data property because all you are doing is passing values along to your search component.
computed:{
values(){
return {
id: this.organisation.id
search: this.organisation.name
}
}
}