Home » vue » Vuex action changes component data

Vuex action changes component data

Posted by: admin November 26, 2021 Leave a comment

Questions:

I have the following component which is just a simple login form:

<template>
    <v-card>
        <v-card-title primary-title class="headline white--text primary">Login</v-card-title>
        <form @keyup.enter="submit( form )" @submit.prevent="submit( form )">
            <v-card-text>
                <v-text-field
                    label="Email address"
                    required
                    :error-messages="errors['email']"
                    v-model="form.email"
                />

                <v-text-field
                    label="Password"
                    hint="At least 8 characters"
                    minlength="8"
                    :append-icon="passwordVisible ? 'visibility_off' : 'visibility'"
                    :append-icon-cb="() => (passwordVisible = !passwordVisible)"
                    :type="passwordVisible ? 'text' : 'password'"
                    required
                    counter
                    :error-messages="errors['password']"
                    v-model="form.password"
                />
            </v-card-text>

            <v-card-actions>
                <v-btn type="submit" color="primary">Login</v-btn>
            </v-card-actions>
        </form>
    </v-card>
</template>

<script>
    export default
    {
        metaInfo: {
            title: 'Login'
        },

        data()
        {
            return {
                form: {
                    email: '',
                    password: '',
                },
                passwordVisible: false,
            }
        },

        computed: {    
            errors()
            {
                return this.$store.getters.errors;
            }
        },

        methods: {
            submit( data )
            {
                this.$store.dispatch( 'userLogin', data );
            }
        },
    }
</script>

The userLogin action is the looks like:

userLogin( context, data )
        {
            // Base64 encode password
            data.password = btoa( data.password );

            fetch( '/api/users/login', {
                headers: {
                    'X-Requested-With': 'XMLHttpRequest',
                    'X-CSRF-token': window.token,
                    'Content-Type': 'application/json',
                    'Accept': 'application/json'
                },
                method: 'POST',
                body: JSON.stringify( data )
            })
                .then( response => {
                    return response.json();
                })
                .then( response => {
                    // If there are any errors
                    if( response.errors )
                    {
                        context.commit( 'errors', response.errors );
                    }

                    context.commit( 'message', response.message );
                    context.commit( 'success', response.success ? response.success : false );

                    if( response.token )
                    {
                        context.commit( 'userLogin', response.token );
                    }
                })
                .catch( error => {
                    console.error( 'userLogin', error );
                });
        }

Everything in the form is working as expected until I try to login. When the form is submitted the action runs 2 times. On the first go I get the expected result which is a token generated by the API. However the second time the password field in the template has changed to the base64 value.

So my questions are why is the action executed twice? Why does the password field value change when the value is only “stored and read” from within the component?

I hope someone can clear things up for me.

Answers:

why is the action executed twice

You have two event listeners attached on the form:

  1. @submit.prevent
  2. @keyup.enter

The event handler for both these event is submit( form ) which is dispatching the action

When you are focused in an input field and hit the enter button then the form will be submitted implicitly.
Therefore the submit( form ) will be called twice ;

  1. Form’s submit method being called
  2. Since the enter button is pressed the event handler for @keyup.enter is also called

Why does the password field value change when the value is only “stored and read” from within the component?

You are passing the form as payload to the action which is an object:

form: { email: '', password: '', }

In JavaScript objects are passed by reference.
In your action you are mutation the form object’s password property by

data.password = btoa( data.password );

So the changes are reflected in the component also

———————————-

SOLITION

why is the action executed twice

Since the default behavior of hitting enter when an input is in focus is submitting the form you can remove @keyup.enter listener

Why does the password field value change when the value is only "stored and read" from within the component?

Do not mutate the original object. Instead create a new one and pass that as your post request body

// Base64 encode password
let details = {
        email: data.email, 
        password: btoa( data.password )
    }

fetch( '/api/users/login', {
    headers: {
        'X-Requested-With': 'XMLHttpRequest',
        'X-CSRF-token': window.token,
        'Content-Type': 'application/json',
        'Accept': 'application/json'
    },
    method: 'POST',
    body: JSON.stringify( details )
})