I have a user profile section and Im trying to allow the user to edit their information. I am using vuex to store the user profile data and pulling it into the form. The edit form is located in a child component of the userProfile component - which loads the data save commits it to VUEX.
So I can populate the form with the data from VUEX, but as soon as I change any values in the form, it changes the value in my parent component as well.
I am not committing changes to VUEX until the form is saved, so it means the data is bound two way to VUEX. I was under the impression this was not possible. In this case it is not desired since if the user changes some data, then navigates away without actually clicking "save", the data is VUEX is still changed.
Note, this is a simplified example. Im actually using router view to load the child component, or I would pass the data through props. I have tested loading the edit-profile component directly like it is below, and I have the same issue.
Please see the code below, I can't find why the data is being sent back up to the store. Any help is greatly appreciated.
In the parent, I set retrieve the user data like so:
<template>
<div class="content">
<h1>{{getUserDetails.firstname}} {{getUserDetails.lastname}} </h1>
<edit-profile></edit-profile>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import EditProfile from './Edit.vue';
export default {
data() {
return {
// data
}
},
created () {
this.fetchData();
},
components: {
EditProfile:EditProfile
},
computed: mapGetters([
'getUserDetails'
]),
methods: {
fetchData: function () {
var _this = this;
// ajax call - then
_this.$store.commit('setData', {
name: 'userDetails',
data: response.data.data.userDetails
});
}
}
}
</script>
This loads the results and stores them in the store, and works great.
My store has this:
const store = new Vuex.Store({
state: {
userDetails: {}
},
mutations: {
setData(state, payload){
state[payload.name] = payload.data;
}
},
getters: {
getUserDetails: state => {
return state.userDetails;
}
}
}
Everything here is working.
In my child component with the edit form, I am populating the form like this:
<template>
<form>
<label>Name</label>
<input name="firstname" v-model="profile.firstname">
<input name="lastname" v-model="profile.lastname">
<button v-on:click="save">submit</button>
</form>
</template>
<script>
import {mapGetters } from 'vuex';
export default {
data() {
return {
profile:{}
}
},
watch: {
getUserDetails (newData){
this.profile = newData;
}
},
created (){
this.profile = this.$store.getters.getUserDetails;
},
computed: mapGetters([
'getUserDetails'
]),
methods:{
save (){
var _this = this;
// ajax call to POST this.profile then
_this.$store.commit('setData', {
name: 'userDetails',
data: this.profile
});
}
}
}
</script>
If you are looking for a non binding solution with vuex you can clone the object and use the local version for v-model than on submit commit it.
in your created lifecycle function do this:
Why I think it is not working as expected for you: you're receiving an object, binding it to a local property. Then when you change that local property, it's bound by object pointer (/memory address) to the store's object. Creating a new object and setting the properties on that new object based on the properties of the state's user profile object should do the trick, since the new object would have it's own address in memory, would point to another place...
Illustration:
However if those properties (firstName, lastName) are objects or arrays (anything accessed by pointer to memory address) then this wouldn't work either.
So... what I'd most likely end up doing myself in this situation is something like this:
This defines local properties. When loading the data, you would populate the local values with profile data you have in the Vuex store.
Then, when saving, you use your local variables to update the store's values.
I'm writing this from the top of my head, so there might be a bug or typo in here... But I hope at the very least my logic is correct ;-P and clear to you.
Pro: until you're ready to save the edited information, you're not reflecting it anywhere else.
Con: if you'd need to reflect temporary changes (maybe in a User Profile Preview area), this might or might not work depending on your app's structure. You might want to bind or save on @input to a state.temporaryUserProfile object in that case?
I am still new to Vue.js, started using it 2 weeks ago. Hope this is clear and correct :)
The problem is caused by using v-model with mapGetters - this creates the two-way binding you've described. The simple solution is to use value instead:
This way the form is only changing the local copy of field and not pushing the changes back to the Vuex store.