How to create Vuejs controlled input form?

Viewed 267

I'm used to creating controlled components in react using state; however, I'm new to Vue and would like to learn how I could recreate my code from react into something Vue could use. I want the input fields values to be store in a piece of data so that I could do something with them.

<template>
    <form @submit="handleSubmit" class="form form--signin">
        <h2 class="form__h2">Log into your account</h2>

        <label for="email">Email address</label>
        <input v-model="email" type="email" id="email" autoComplete="off"/>

        <label for="password">Password</label>
        <input v-model="password" type="password" id="password" autoComplete="off"/>

        <button class="form__btn">Login</button>
    </form>
</template>

<script>

import axios from 'axios';

export default {
    name: 'SignIn',
    data() {
        return {
            user: null,
            email: null,
            password: null
        }
    },
    methods: {
        handleSubmit(event) {
            event.preventDefault();
            this.login(this.email, this.password);
            this.email = '';
            this.password = '';
        },
        login(email, password) {
            axios.post('http://localhost:8000/api/v1/users/login', {
                email,
                password
            })
            .then(res => {
                // console.log(this.user);
                this.user = res.data.data.user;
                if(res.data.status === 'success') {
                    // setUserStatus(res.data.status);
                    window.setTimeout(() => {
                        window.location.assign('/');
                    }, 1500)
                }
            })
            .catch(err => console.log(err.response.data.message));
        }
    }, 
    mounted() {
        if (!this.user) return;

        console.log('Writing to session storage...');
        sessionStorage.setItem('user', JSON.stringify(this.user));
    }
}

3 Answers

This is how it would look like in Vue

Vue.config.devtools = false;
Vue.config.productionTip = false;

const SignIn = Vue.component('sign-in', {

  template: `<form @submit="handleSubmit" className="form form--signin">
            <h2 className="form__h2">Log into your account</h2>

            <label htmlFor="email">Email address</label>
            <input v-model="email" type="email" id="email" autoComplete="off"/>

            <label htmlFor="password">Password</label>
            <input v-model="password" type="password" id="password" autoComplete="off"/>

            <button className="form__btn">Login</button>
        </form>`,
  data() {
    return {
      user: null,
      email: null,
      password: null,
    }
  },
  methods: {
    handleSubmit(event) {

      this.login(this.email, this.password)
      this.email = '';
      this.password = '';
      
      event.preventDefault();
    },
    login(email, password) {

    }
  },
  mounted() {
    if (!this.user)
      return;

    console.log('Writing to session storage...');
    sessionStorage.setItem('user', JSON.stringify(user));
  }
})

var app = new Vue({
  el: '#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <sign-in></sign-in>
</div>

This is how I might approach writing the same component in Vue:

<template>
  <form @submit.prevent="handleSubmit" class="form form--signin">
    <h2 class="form__h2">Log into your account</h2>

    <label for="email">Email address</label>
    <input v-model="email" type="email" id="email" autocomplete="off">

    <label for="password">Password</label>
    <input v-model="password" type="password" id="password" autocomplete="off">

    <button class="form__btn">Login</button>
  </form>
</template>

<script>
const login = async (email, password) => {
  // make the api call and return a promise
};

export default {
  name: "SignIn",
  data: () => ({
    email: "",
    password: ""
  }),
  methods: {
    async handleSubmit(event) {
      console.log("Getting user from API...");

      try {
        const user = await login(this.email, this.password);

        console.log("Writing to session storage...");
        sessionStorage.setItem("user", JSON.stringify(user));

        this.email = "";
        this.password = "";
      } catch (e) {
        // handle login errors
      }
    }
  }
};
</script>

The form controls' state is stored on the data property of the component, and a two-way binding is set up on the controls with the v-model directive. Any changes to the inputs will be synced to the component's state, and vice versa.

Vue provides some handy modifiers when adding event listeners to the template, so by writing @submit.prevent the default submit handler is prevented from firing.

I've created a sandbox example, so you can easily play around with the code and see it in action.

You can use v-model directive to bind component data and v-on directive to bind component methods:

<template>
  <div>
    <div class>Login Form</div>

    <form v-on:submit="handleSubmit">
      <input v-model="email" placeholder="email" type="text" class="input" />
      <br />
      <input v-model="password" placeholder="password" type="password" class="input" />
      <br />
      <button v-on:click="handleSubmit" type="submit" class="submit">Login</button>
    </form>

    <hr />

    <div>Email: {{email}} and Password: {{password}}</div>
    <div>User sessions: {{user}}</div>
  </div>
</template>

And can use axios to make API call to authenticate and store user session data from response:

<script>
import axios from "axios";
export default {
  name: "LoginForm",
  props: {},
  data: () => ({
    email: "",
    password: "",
    user: {}
  }),
  methods: {
    login() {
      axios
        .post("http://localhost:8000/api/v1/users/login", {
          email: this.email,
          password: this.password
        })
        .then(res => {
          console.log(res);
          this.user = res.data.data.user;
          sessionStorage.setItem("user", JSON.stringify(this.user));
          if (res.data.status === "success") {
            window.setTimeout(() => {
              window.location.assign("/"); // to verify mounted hook
            }, 1500);
          }
        })
        .catch(err => console.log(err.response.data.message));
    },
    handleSubmit: function(e) {
      e.preventDefault();
      this.login();
    }
  },
  mounted() {
    console.debug("mounted");
  }
};
</script>

Also, note that you can use mounted lifecycle hooks to read data from localStorage or any other of your choice.

You can use async / await or then / catch to handle asynchronous (promise based) API calls.

Here is CodeSandbox.


Here is how to use mounted hook same as useEffect(() => {}, []) in react:

mounted() {
    const user = sessionStorage.getItem("user");
    if (user) {
      this.user = JSON.parse(user);
    }
  }
Related