0
votes

I have a simple button component for submitting forms and it should give a bit of feedback when clicked on it: show a spinner and disable the button while the form is being submitted and then also keep it disabled if there are any errors returned from the server until the errors are cleared (errors are cleared by changing the input of fields that have them).

Here's the template of the component:

<template>
    <button type="submit" :disabled="disabled">
        <loading-spinner v-if="submitting"></loading-spinner>
        <slot>Submit</slot>
    </button>
</template>

<script>
    export default {
        props: {
            form: {
                type: Object,
                required: true
            }
        },
        created() {
            // Globally available event handler which listens for events 
            // emitted by Form object - it works fine
            Event.$on('submitting', () => this.submitting = true);
            Event.$on('submitted', () => this.submitting = false);
        },
        data() {
            return {
                submitting: false,
            }
        },
        computed: {
            // Here is the problem: this.form.errors.any() doesn't get updated in the 
            // computed prop even when errors are cleared. This same method, however,
            // returns correct result when called directly via a test button
            disabled() {
                return this.submitting || this.form.errors.any()
            },
              // doesn't get updated either - apparently
//            errors() {
//                return this.form.errors.any()
//            }
        },
    }
</script>

The button gets Form object passed to so it can also have access to Errors object which has some convenient methods to manage the errors. The Form object that is passed as prop is reactive inside the submit-button component and its fields get updated in Vue Devtools as I type into the form (on parent component), but the computed prop (disabled or errors) that relies upon it doesn't get updated.

When I submit the form with wrong data, I get errors, so form.errors.any() returns true. However, once errors are returned the first time, the button turns disabled and it "freezes" like that (form.errors.any() keeps returning true even when it isn't).

I know computed props are cached, but by definition they should update when their dependency gets updated (in this case the computed property disabled should get updated because this.form.errors.any() is updated). However, it seems that submit-button component "ignores" the update of this.form.errors.any() in its computed property (it keeps returning true once errors are present and it never gets updated). The same thing happens if I make a same computed property on parent component.

Additional info

When I manually check its value (e.g. via a button that calls form.errors.any()), it logs the correct value to the console every time, but it doesn't get updated in my computed property.

The any() method on Errors object - nothing fancy here

any() {
    return Object.keys(this.errors).length > 0;
}

The Form and Errors object both work fine: their methods return normal values when called directly (via a button click, for example) from any component and they dispatch events as expected, they just don't react on a computed prop.

Workaround

The solution for me was to use a method instead of computed property, but it's a bit too verbose and I'd mostly like to know why can't I use more elegant computed property in this case?

<template>
    <button type="submit" :disabled="submitting || anyErrors()">
        <loading-spinner v-if="submitting"></loading-spinner>
        <slot>Submit</slot>
    </button>
</template>

<script>
    export default {
        ...

        methods: {
            anyErrors() {
                return this.form.errors.any()
            }
        }
    }
</script>

1
I'd say it's caused by mutating an object in a way that Vue cannot detect (see docs). How are you populating the errors in the Errors object? Are you adding new properties to the object?Decade Moon
@DecadeMoon the errors is an object inside the Errors class. It's not a Vue component, however, if you asked that (since I understood the chapter in docs refers to that). The this.errors is an empty object on instatiation (constructor) and it can be accessed/populated/cleared through methods (get/register/clear and already mentioned any).tal3nce

1 Answers

0
votes

You need to place the form.errors property on your props for form in order to have it reactive out of the box. Otherwise, when you add errors, you have to use the Vue.set() method in order to make it reactive.

So your props object should look like:

props: {
     form: {
         type: Object,
         required: true,
         errors: {},
     }
},

More info: https://vuejs.org/v2/guide/reactivity.html

(edited to make errors an object, as indicated in the question, instead of an array)