11

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>

4 Answers 4

13

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:

created (){
    this.profile = Object.assign({}, this.$store.getters.getUserDetails);
},
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks. This works well enough, although it seems like there should be a more elegant way to do this.
Future users, beware, it just do Shallow Copy of your JSON. Understand Deep vs Shallow Copy here: we-are.bookmyshow.com/…
1

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:

created (){
    // create a new object with {...}
    this.profile = {
        // assign values to properties of same name
        firstName: this.$store.getters.getUserDetails.firstName,
        lastName: this.$store.getters.getUserDetails.lastName,
    };
},

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:

data() {
    return {
        firstName: '',
        lastName: ''
    }
},

This defines local properties. When loading the data, you would populate the local values with profile data you have in the Vuex store.

created() {
    let profile = this.$store.getters.getUserDetails;
    if (profile.firstName && profile.lastName) {
        this.firstName = profile.firstName;
        this.lastName = profile.lastName;
    }
},

Then, when saving, you use your local variables to update the store's values.

methods: {
    save() {
        let profile = {
            firstName: this.firstName,
            lastName: this.lastName
        };

        // ajax call to POST this.profile then
        this.$store.commit('setData', {
            name: 'userDetails',
            data: profile
        });
    }
}

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 :)

Comments

0

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:

:value="profile.firstName"

This way the form is only changing the local copy of field and not pushing the changes back to the Vuex store.

1 Comment

No, don't use value with Vue, there is no binding with the value entered by the user and the object will not be updated so you won't know the values entered, the single way binding should be done in between the store and the component
0

@AfikDeri solution is great, but it only create a shallow copy(for example it wont work if you have nested objects, which is common to have), to solve this you may serialize then parse your vuex state object getUserDetails, as follow:

created (){
    this.profile = JSON.parse(JSON.stringify(this.$store.getters.getUserDetails));
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.