Search code examples
vue.jspasswordsobfuscationvue-props

VueJS toggle password visibilty without mutating the "type" property


I have a basic input component I am working with that has type as a property and up until now has been working very well. However, trying to use it for passwords and implementing obfuscation has been sort of tricky.

How can I toggle hide/show of the password without mutating the prop? I figured making it type = 'password' to type = 'text was the best way, but clearly not.

I've made a Codesandbox to replicate that part of the component, but any advice or direction would be greatly appreciated!

PasswordInput.vue:

<template>
  <div>
    <input :type="type" />
    <button @click="obfuscateToggle" class="ml-auto pl-sm _eye">
      <div>
        <img :src="`/${eyeSvg}.svg`" alt="" />
      </div>
    </button>
  </div>
</template>

<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      passwordVisible: false,
      eyeSvg: "eye-closed",
    };
  },
  props: {
    type: { type: String, default: "text" },
  },
  methods: {
    obfuscateToggle() {
      if (this.eyeSvg === "eye-closed") {
        this.eyeSvg = "eye";
      } else this.eyeSvg = "eye-closed";
      // this.eyeSvg = "eye-closed" ? "" : (this.eyeSvg = "eye");
      if ((this.type = "password")) {
        this.type = "text";
      } else this.type = "password";
    },
  },
};
</script>

App.vue

<template>
  <div id="app">
    <PasswordInput type="password" />
  </div>
</template>

Solution

  • The only way to do it is by mutating the type attribute. As that is how the browser decides the render it as either just a textbox or as a password. Therefore you are doing this the right way.

    The one issue that you will encounter is that you will have errors thrown in your console because you are attempting to mutate a prop.

    This is quick and easy to fix. First, you will create a new data property, and assign it to the default of type

    data(){
       return{
          fieldType:'text'
       }
    }
    

    Then you will use the on mounted lifecycle hook, and update your data property to match your prop's value`

    mounted(){
       this.fieldType = this.type;
    }
    

    If you know the type prop will change from the parent component you can also use a watcher for changes and assign type

    watch:{
       type(val){
          this.fieldType = val;
       }
    }
    

    You will then update your obfuscateToggle method to use the fieldtype variable:

    obfuscateToggle() {
      if (this.eyeSvg === "eye-closed") {
        this.eyeSvg = "eye";
      } else this.eyeSvg = "eye-closed";
    
    
      //You can simplify this by using this.fieldType = this.fieldType == "text" ? "password" : "text"
      if (this.fieldType == "password") {
        this.fieldType = "text";
      } else this.fieldType = "password";
    }
    

    Finally, in your template, you will want to change type to fieldType

    <template>
      <div>
        <input :type="fieldType" />
        <button @click="obfuscateToggle" class="ml-auto pl-sm _eye">
          <div>
            <img :src="`/${eyeSvg}.svg`" alt="" />
          </div>
        </button>
      </div>
    </template>
    

    Putting it all together

    <script>
    export default {
      name: "HelloWorld",
      data() {
        return {
          passwordVisible: false,
          eyeSvg: "eye-closed",
          fieldType: "text"
        };
      },
      props: {
        type: { type: String, default: "text" },
      },
      methods: {
        obfuscateToggle() {
          if (this.eyeSvg === "eye-closed") {
            this.eyeSvg = "eye";
          } else this.eyeSvg = "eye-closed";
     
    
          //You can simplify this by using this.fieldType = this.fieldType == "text" ? "password" : "text"
          if (this.fieldType == "password") {
            this.fieldType = "text";
          } else this.fieldType = "password";
    
    
        },
      },
      watch:{
        type(val){
           this.fieldType = val;
        }
      },
      mounted(){
        this.fieldType = this.type;
      },
    };
    </script>
    

    Here is an example on CodeSandBox


    Also, you had a small typo in your obfuscateToggle method.

    if(this.type = 'password')
    

    this was assigning type instead of comparing it against a literal :)