1
votes

Okay so I followed along with the entire Vue Laracasts videos, which ends up having a Form and an Errors class. Here's a github link to the repo from the video:

https://github.com/laracasts/Vue-Forms/blob/master/public/js/app.js

Now what I'm trying to do is integrate Vuex to keep track of my currently logged in user, prefill a form with the current values, and show errors if any of the Laravel validation fails.

The problem I seem to be running into is that I have to set my authUser as a computed property, to pull it from the Vuex store. Then since I can't use these in anything set in data I had to move my form to a computed property. Now that it's a computed property it never gets updated so my form never has errors even when errors are being thrown.

Here's my route file Account.vue

<template>
<div>
    <form @submit.prevent="onSubmit" @keydown="form.errors.clear($event.target.name)">
        <div class="row">
            <div class="form-group col-md-6 col-lg-3">
                <label>Name</label>
                <input type="text" class="form-control" name="user" autocomplete="false" v-model="form.name">
                <span class="help is-danger" v-if="form.errors.has('name')" v-text="form.errors.get('name')"></span>
            </div>
            <div class="form-group col-md-6 col-lg-3">
                <label>Email address</label>
                <input type="email" class="form-control" placeholder="Enter email" autocomplete="false" name="email" v-model="form.email">
                <span class="help is-danger" v-if="form.errors.has('email')" v-text="form.errors.get('email')"></span>
            </div>
            <div class="form-group col-lg-3">
                <label>Password</label>
                <input type="password" class="form-control" placeholder="Password" autocomplete="false" name="password" v-model="form.password">
                <span class="help is-danger" v-if="form.errors.has('password')" v-text="form.errors.get('password')"></span>
            </div>
        </div>

        <div class="col-12">
            <button type="submit" class="btn btn-primary">Submit</button>
        </div>
    </form>
</div>
</template>

<script>
export default {
    computed: {
        authUserName: {
            get: function() {
                return this.$store.getters.getAuthUser.name;
            },
            set: function(value) {
                this.$store.commit('setAuthUserValue', { key: 'name', value })
            }
        },
        authUserEmail: {
            get: function() {
                return this.$store.getters.getAuthUser.email;
            },
            set: function(value) {
                this.$store.commit('setAuthUserValue', { key: 'email', value })
            }
        },
        form() {
            return new Form({
                name: this.authUserName,
                email: this.authUserEmail,
                password: ''
            })
        }
    },

    data() {
        return {

        }
    },

    mounted() {
    },

    methods: {
        onSubmit() {
            this.form
                .patch('/current-user')
                .then(status => {
                    this.$emit('success', status);
                });
        }
    }
}
</script>

index.js

import AuthUser from "../models/AuthUser";

export default new Vuex.Store({
state: { // = data
    authUser: {
        name: null,
        email: null,
    }
},

getters: { // = computed properties
    getAuthUser(state, getters) {
        return state.authUser;
    }
},

actions: { // = methods
    fetchAuthUser(context) {
        AuthUser.load(user => {
            context.commit('setAuthUser', user);
        });
    }
},

mutations: {
    setAuthUser(state, user) {
        Vue.set(state, 'authUser', user);
    },
    setAuthUserValue(state, payload) {
        state.authUser = { ...state.authUser, payload }
    }
}
});

app.js

import router from './routes';
import Hamburger from './components/Hamburger';
import NavHeader from './components/NavHeader';
import store from "./store/index";

new Vue({

el: '#app',
components: {
    Hamburger,
    NavHeader
},

data() {
    return {

    }
},

created() {
    store.dispatch('fetchAuthUser');
},
router,
store
});

I've tried messing around with watchers, but everything threw errors. I'm trying really hard to get the form to be created with the existing users values preset, and was recommended to try vuex, but am now running into these issues where form now needs to be computed instead of set in data, so form never has errors now. Sorry, pulling my hair out learning all these new things at once lol.

EDIT

I've now also tried setting form to an empty form object in data, then setting the properties in mounted(), but no matter how I try to access authUser in mounted I'm getting undefined.

mounted() {
    console.log(this.authUser);
    console.log(this.authUser.name);
    console.log(this.$store.getters.getUser.name);
},

outputs...

{ob: Observer}

undefined

undefined

EDIT 2

Latest code updated, still not getting the errors to show up below the input, although they do show up under the dev tools in.. Root > Account > computed > form > errors > errors

In there it lists name and email errors if I left them blank. Could it be cause it's double nested? I know this is a Laravel issue with how it nests the json response.

Perhaps something in that Form or Errors class in app.js?

1
Shouldn't authUser: [] be authUser: {} ? - Derek Pollard
if so, you should set its state within setUser using: Vue.set(state, 'authUser', user); - Derek Pollard
Changed those two lines, still same issues. Can't update the form since it's now a computed property, since I can't set the forms default name and email to the stored authUser name and email without setting it as computed? - Octoxan
Take a look into vue lifecycle diagram. Instead of using computed properties to set form values, you should use data, and assign initial values from the store in the created or mounted method (or any other available method ) - ljubadr
@ljubadr hm I tried that, by creating an empty form object in data and then setting it's values. Any idea why this.authUser.name would be undefined when I go to call it in mounted or created? - Octoxan

1 Answers

1
votes

AuthUser.load is an asynchronous function, as you're executing it in the created hook, it's not guaranteed that authUsers values will be set by the time that the Account.vue's created hook is fired.

Now the issue that you're having is due to the fact that you have a computed property with no setter. The Form class attempts to mutate the value of the store directly, which will result in the console exploding.

If you provide a setter for your value, when you attempt to write to it, you can react to the value being passed to the setter and you can dispatch (or in your case, commit) that value to the store:

computed: {
   authUser: {
      get(): {
          return this.$store.getters.getUser;
      },
      set(value): {
          this.$store.commit('setUser', value)
      }
   }
}

But wait, this still won't work! Why? Because the value you will receive will just be the text input value, how will you know which property within the AuthUser to update? Probably the easiest way to resolve that is to create a computed property for each property on the authUser

computed: {
   authUserName: {
      get(): {
          return store.getters.getUser.name;
      },
      set(value): {
          this.$store.commit('setUser', { key: 'name', value })
      }
   },
   authUserEmail: {
      get(): {
          return store.getters.getUser.email;
      },
      set(value): {
          this.$store.commit('setUser', { key: 'email', value })
      }
   },
   authUserPassword: {
      get(): {
          return store.getters.getUser.password;
      },
      set(value): {
          this.$store.commit('setUser', { key: 'password', value })
      }
   }
}

Now you can instantiate the form with each of the getters:

new Form({
    name: this.authUserName,
    email: this.authUserEmail,
    password: this.authUserPassword
})

But, yet again, this still won't work! We need a new mutation that will only write properties of our authUser

setUserValue(state, payload) {
   state.authUser = { ...state.authUser, payload }
}

By using a simple destructure we can overwrite the properties of our authUser and merge in the new value of the computed property.

Now you need to update your computed properties and use this.$store.commit('setUserValue', { key: 'name/email/password': value}) in your setter instead.

Finally, your authUser should be an object with preset properties:

authUser: {
    name: null,
    email: null,
    password: null
}

This is important as properties of an object are reactive. However, if properties are added to the object, you cannot simply overwrite the object and have new reactive properties. You would need to use Vue.set() for this, such as:

setUser(state, user) {
    Vue.set(state, 'authUser', user)
}

This would correctly allow for the observed properties to become reactive. This is an important concept in the reactivness of the VueX store. This is also the reason why your values are undefined.